Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>/**
* Creates the symbol table of variables available in the current scope and
* their types.
*
* Scopes created by this class are very different from scopes created
* by the syntactic scope creator. These scopes have type information, and
* include some qualified names in addition to variables
* (like Class.staticMethod).
*
* When building scope information, also declares relevant information
* about types in the type registry.
*
* @author nicksantos@google.com (Nick Santos)
*/
final class TypedScopeCreator implements ScopeCreator {
/**
* A suffix for naming delegate proxies differently from their base.
*/
static final String DELEGATE_PROXY_SUFFIX =
ObjectType.createDelegateSuffix("Proxy");
static final DiagnosticType MALFORMED_TYPEDEF =
DiagnosticType.warning(
"JSC_MALFORMED_TYPEDEF",
"Typedef for {0} does not have any type information");
static final DiagnosticType ENUM_INITIALIZER =
DiagnosticType.warning(
"JSC_ENUM_INITIALIZER_NOT_ENUM",
"enum initializer must be an object literal or an enum");
static final DiagnosticType CTOR_INITIALIZER =
DiagnosticType.warning(
"JSC_CTOR_INITIALIZER_NOT_CTOR",
"Constructor {0} must be initialized at declaration");
static final DiagnosticType IFACE_INITIALIZER =
DiagnosticType.warning(
"JSC_IFACE_INITIALIZER_NOT_IFACE",
"Interface {0} must be initialized at declaration");
static final DiagnosticType CONSTRUCTOR_EXPECTED =
DiagnosticType.warning(
"JSC_REFLECT_CONSTRUCTOR_EXPECTED",
"Constructor expected as first argument");
static final DiagnosticType UNKNOWN_LENDS =
DiagnosticType.warning(
"JSC_UNKNOWN_LENDS",
"Variable {0} not declared before @lends annotation.");
static final DiagnosticType LENDS_ON_NON_OBJECT =
DiagnosticType.warning(
"JSC_LENDS_ON_NON_OBJECT",
"May only lend properties to object types. {0} has type {1}.");
private final AbstractCompiler compiler;
private final ErrorReporter typeParsingErrorReporter;
private final TypeValidator validator;
private final CodingConvention codingConvention;
private final JSTypeRegistry typeRegistry;
private final List<ObjectType> delegateProxyPrototypes = Lists.newArrayList();
private final Map<String, String> delegateCallingConventions =
Maps.newHashMap();
// Simple properties inferred about functions.
private final Map<Node, AstFunctionContents> functionAnalysisResults =
Maps.newHashMap();
/**
* Defer attachment of types to nodes until all type names
* have been resolved. Then, we can resolve the type and attach it.
*/
private class DeferredSetType {
final Node node;
final JSType type;
DeferredSetType(Node node, JSType type) {
Preconditions.checkNotNull(node);
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>
Preconditions.checkNotNull(type);
this.node = node;
this.type = type;
// Other parts of this pass may read off the node.
// (like when we set the LHS of an assign with a typed RHS function.)
node.setJSType(type);
}
void resolve(Scope scope) {
node.setJSType(type.resolve(typeParsingErrorReporter, scope));
}
}
TypedScopeCreator(AbstractCompiler compiler) {
this(compiler, compiler.getCodingConvention());
}
TypedScopeCreator(AbstractCompiler compiler,
CodingConvention codingConvention) {
this.compiler = compiler;
this.validator = compiler.getTypeValidator();
this.codingConvention = codingConvention;
this.typeRegistry = compiler.getTypeRegistry();
this.typeParsingErrorReporter = typeRegistry.getErrorReporter();
}
/**
* Creates a scope with all types declared. Declares newly discovered types
* and type properties in the type registry.
*/
@Override
public Scope createScope(Node root, Scope parent) {
// Constructing the global scope is very different than constructing
// inner scopes, because only global scopes can contain named classes that
// show up in the type registry.
Scope newScope = null;
AbstractScopeBuilder scopeBuilder = null;
if (parent == null) {
// Run a first-order analysis over the syntax tree.
(new FirstOrderFunctionAnalyzer(compiler, functionAnalysisResults))
.process(root.getFirstChild(), root.getLastChild());
// Find all the classes in the global scope.
newScope = createInitialScope(root);
GlobalScopeBuilder globalScopeBuilder = new GlobalScopeBuilder(newScope);
scopeBuilder = globalScopeBuilder;
NodeTraversal.traverse(compiler, root, scopeBuilder);
} else {
newScope = new Scope(parent, root);
LocalScopeBuilder localScopeBuilder = new LocalScopeBuilder(newScope);
scopeBuilder = localScopeBuilder;
localScopeBuilder.build();
}
scopeBuilder.resolveStubDeclarations();
scopeBuilder.resolveTypes();
// Gather the properties in each function that we found in the
// global scope, if that function has a @this type that we can
// build properties on.
for (Node functionNode : scopeBuilder.nonExternFunctions) {
JSType type = functionNode.getJSType();
if (type != null && type.isFunctionType()) {
FunctionType fnType = type.toMaybeFunctionType();
ObjectType fnThisType = fnType.getTypeOfThis();
if (!fnThisType.isUnknownType()) {
NodeTraversal.traverse(compiler, functionNode.getLastChild(),
scopeBuilder.new CollectProperties(fnThisType));
}
}
}
if (parent == null) {
codingConvention.defineDelegateProxyPrototypeProperties(
typeRegistry, newScope, delegateProxyPrototypes,
delegateCallingConventions);
}
return newScope;
}
/**
* Patches a
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> given global scope by removing variables previously declared in
* a script and re-traversing a new version of that script.
*
* @param globalScope The global scope generated by {@code createScope}.
* @param scriptRoot The script that is modified.
*/
void patchGlobalScope(Scope globalScope, Node scriptRoot) {
// Preconditions: This is supposed to be called only on (named) SCRIPT nodes
// and a global typed scope should have been generated already.
Preconditions.checkState(scriptRoot.isScript());
Preconditions.checkNotNull(globalScope);
Preconditions.checkState(globalScope.isGlobal());
String scriptName = NodeUtil.getSourceName(scriptRoot);
Preconditions.checkNotNull(scriptName);
for (Node node : ImmutableList.copyOf(functionAnalysisResults.keySet())) {
if (scriptName.equals(NodeUtil.getSourceName(node))) {
functionAnalysisResults.remove(node);
}
}
(new FirstOrderFunctionAnalyzer(
compiler, functionAnalysisResults)).process(null, scriptRoot);
// TODO(bashir): Variable declaration is not the only side effect of last
// global scope generation but here we only wipe that part off!
// Remove all variables that were previously declared in this scripts.
// First find all vars to remove then remove them because of iterator!
Iterator<Var> varIter = globalScope.getVars();
List<Var> varsToRemove = Lists.newArrayList();
while (varIter.hasNext()) {
Var oldVar = varIter.next();
if (scriptName.equals(oldVar.getInputName())) {
varsToRemove.add(oldVar);
}
}
for (Var var : varsToRemove) {
globalScope.undeclare(var);
globalScope.getTypeOfThis().removeProperty(var.getName());
}
// Now re-traverse the given script.
GlobalScopeBuilder scopeBuilder = new GlobalScopeBuilder(globalScope);
NodeTraversal.traverse(compiler, scriptRoot, scopeBuilder);
}
/**
* Create the outermost scope. This scope contains native binding such as
* {@code Object}, {@code Date}, etc.
*/
@VisibleForTesting
Scope createInitialScope(Node root) {
NodeTraversal.traverse(
compiler, root, new DiscoverEnumsAndTypedefs(typeRegistry));
Scope s = new Scope(root, compiler);
declareNativeFunctionType(s, ARRAY_FUNCTION_TYPE);
declareNativeFunctionType(s, BOOLEAN_OBJECT_FUNCTION_TYPE);
declareNativeFunctionType(s, DATE_FUNCTION_TYPE);
declareNativeFunctionType(s, ERROR_FUNCTION_TYPE);
declareNativeFunctionType(s, EVAL_ERROR_FUNCTION_TYPE);
declareNativeFunctionType(s, FUNCTION_FUNCTION_TYPE);
declareNativeFunctionType(s, NUMBER_OBJECT_FUNCTION_TYPE);
declareNativeFunctionType(s, OBJECT_FUNCTION_TYPE);
declareNativeFunctionType(s, RANGE_ERROR_FUNCTION_TYPE);
declareNativeFunctionType(s, REFERENCE
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>_ERROR_FUNCTION_TYPE);
declareNativeFunctionType(s, REGEXP_FUNCTION_TYPE);
declareNativeFunctionType(s, STRING_OBJECT_FUNCTION_TYPE);
declareNativeFunctionType(s, SYNTAX_ERROR_FUNCTION_TYPE);
declareNativeFunctionType(s, TYPE_ERROR_FUNCTION_TYPE);
declareNativeFunctionType(s, URI_ERROR_FUNCTION_TYPE);
declareNativeValueType(s, "undefined", VOID_TYPE);
// ActiveXObject is uniquely special, because it can be used to construct
// any type (the type that it creates is related to the arguments you
// pass to it).
declareNativeValueType(s, "ActiveXObject", NO_OBJECT_TYPE);
return s;
}
private void declareNativeFunctionType(Scope scope, JSTypeNative tId) {
FunctionType t = typeRegistry.getNativeFunctionType(tId);
declareNativeType(scope, t.getInstanceType().getReferenceName(), t);
declareNativeType(
scope, t.getPrototype().getReferenceName(), t.getPrototype());
}
private void declareNativeValueType(Scope scope, String name,
JSTypeNative tId) {
declareNativeType(scope, name, typeRegistry.getNativeType(tId));
}
private void declareNativeType(Scope scope, String name, JSType t) {
scope.declare(name, null, t, null, false);
}
private static class DiscoverEnumsAndTypedefs
extends AbstractShallowStatementCallback {
private final JSTypeRegistry registry;
DiscoverEnumsAndTypedefs(JSTypeRegistry registry) {
this.registry = registry;
}
@Override
public void visit(NodeTraversal t, Node node, Node parent) {
Node nameNode = null;
switch (node.getType()) {
case Token.VAR:
for (Node child = node.getFirstChild();
child != null; child = child.getNext()) {
identifyNameNode(
child, child.getFirstChild(),
NodeUtil.getBestJSDocInfo(child));
}
break;
case Token.EXPR_RESULT:
Node firstChild = node.getFirstChild();
if (firstChild.isAssign()) {
identifyNameNode(
firstChild.getFirstChild(), firstChild.getLastChild(),
firstChild.getJSDocInfo());
} else {
identifyNameNode(
firstChild, null, firstChild.getJSDocInfo());
}
break;
}
}
private void identifyNameNode(
Node nameNode, Node valueNode, JSDocInfo info) {
if (nameNode.isQualifiedName()) {
if (info != null) {
if (info.hasEnumParameterType()) {
registry.identifyNonNullableName(nameNode.getQualifiedName());
} else if (info.hasTypedefType()) {
registry.identifyNonNullableName(nameNode.getQualifiedName());
}
}
}
}
}
private JSType getNativeType(JSTypeNative native
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>Type) {
return typeRegistry.getNativeType(nativeType);
}
private abstract class AbstractScopeBuilder
implements NodeTraversal.Callback {
/**
* The scope that we're building.
*/
final Scope scope;
private final List<DeferredSetType> deferredSetTypes =
Lists.newArrayList();
/**
* Functions that we found in the global scope and not in externs.
*/
private final List<Node> nonExternFunctions = Lists.newArrayList();
/**
* Object literals with a @lends annotation aren't analyzed until we
* reach the root of the statement they're defined in.
*
* This ensures that if there are any @lends annotations on the object
* literals, the type on the @lends annotation resolves correctly.
*
* For more information, see
* http://code.google.com/p/closure-compiler/issues/detail?id=314
*/
private List<Node> lentObjectLiterals = null;
/**
* Type-less stubs.
*
* If at the end of traversal, we still don't have types for these
* stubs, then we should declare UNKNOWN types.
*/
private final List<StubDeclaration> stubDeclarations =
Lists.newArrayList();
/**
* The current source file that we're in.
*/
private String sourceName = null;
/**
* The InputId of the current node.
*/
private InputId inputId;
private AbstractScopeBuilder(Scope scope) {
this.scope = scope;
}
void setDeferredType(Node node, JSType type) {
deferredSetTypes.add(new DeferredSetType(node, type));
}
void resolveTypes() {
// Resolve types and attach them to nodes.
for (DeferredSetType deferred : deferredSetTypes) {
deferred.resolve(scope);
}
// Resolve types and attach them to scope slots.
Iterator<Var> vars = scope.getVars();
while (vars.hasNext()) {
vars.next().resolveType(typeParsingErrorReporter);
}
// Tell the type registry that any remaining types
// are unknown.
typeRegistry.resolveTypesInScope(scope);
}
@Override
public final boolean shouldTraverse(NodeTraversal t, Node n,
Node parent) {
inputId = t.getInputId();
if (n.isFunction() ||
n.isScript()) {
Preconditions.checkNotNull(inputId);
sourceName = NodeUtil.getSourceName(n);
}
// We do want to traverse the name of a named function, but we don't
// want to traverse the arguments or body.
boolean descend = parent == null || !parent.isFunction() ||
n == parent.getFirstChild() || parent == scope.getRootNode();
if (descend) {
// Handle hoisted functions on pre-order traversal, so that they
// get hit before other things in the scope.
if (NodeUtil.isStatementParent
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>(n)) {
for (Node child = n.getFirstChild();
child != null;
child = child.getNext()) {
if (NodeUtil.isHoistedFunctionDeclaration(child)) {
defineFunctionLiteral(child, n);
}
}
}
}
return descend;
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
inputId = t.getInputId();
attachLiteralTypes(t, n);
switch (n.getType()) {
case Token.CALL:
checkForClassDefiningCalls(t, n, parent);
checkForCallingConventionDefiningCalls(n, delegateCallingConventions);
break;
case Token.FUNCTION:
if (t.getInput() == null || !t.getInput().isExtern()) {
nonExternFunctions.add(n);
}
// Hoisted functions are handled during pre-traversal.
if (!NodeUtil.isHoistedFunctionDeclaration(n)) {
defineFunctionLiteral(n, parent);
}
break;
case Token.ASSIGN:
// Handle initialization of properties.
Node firstChild = n.getFirstChild();
if (firstChild.isGetProp() &&
firstChild.isQualifiedName()) {
maybeDeclareQualifiedName(t, n.getJSDocInfo(),
firstChild, n, firstChild.getNext());
}
break;
case Token.CATCH:
defineCatch(n, parent);
break;
case Token.VAR:
defineVar(n, parent);
break;
case Token.GETPROP:
// Handle stubbed properties.
if (parent.isExprResult() &&
n.isQualifiedName()) {
maybeDeclareQualifiedName(t, n.getJSDocInfo(), n, parent, null);
}
break;
}
// Analyze any @lends object literals in this statement.
if (n.getParent() != null && NodeUtil.isStatement(n) &&
lentObjectLiterals != null) {
for (Node objLit : lentObjectLiterals) {
defineObjectLiteral(objLit);
}
lentObjectLiterals.clear();
}
}
private void attachLiteralTypes(NodeTraversal t, Node n) {
switch (n.getType()) {
case Token.NULL:
n.setJSType(getNativeType(NULL_TYPE));
break;
case Token.VOID:
n.setJSType(getNativeType(VOID_TYPE));
break;
case Token.STRING:
n.setJSType(getNativeType(STRING_TYPE));
break;
case Token.NUMBER:
n.setJSType(getNativeType(NUMBER_TYPE));
break;
case Token.TRUE:
case Token.FALSE:
n.setJSType(getNativeType(BOOLEAN_TYPE));
break;
case Token.REGEXP:
n.setJSType(getNativeType(REGEXP_TYPE));
break;
case
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> Token.OBJECTLIT:
JSDocInfo info = n.getJSDocInfo();
if (info != null &&
info.getLendsName() != null) {
if (lentObjectLiterals == null) {
lentObjectLiterals = Lists.newArrayList();
}
lentObjectLiterals.add(n);
} else {
defineObjectLiteral(n);
}
break;
// NOTE(nicksantos): If we ever support Array tuples,
// we will need to put ARRAYLIT here as well.
}
}
private void defineObjectLiteral(Node objectLit) {
// Handle the @lends annotation.
JSType type = null;
JSDocInfo info = objectLit.getJSDocInfo();
if (info != null &&
info.getLendsName() != null) {
String lendsName = info.getLendsName();
Var lendsVar = scope.getVar(lendsName);
if (lendsVar == null) {
compiler.report(
JSError.make(sourceName, objectLit, UNKNOWN_LENDS, lendsName));
} else {
type = lendsVar.getType();
if (type == null) {
type = typeRegistry.getNativeType(UNKNOWN_TYPE);
}
if (!type.isSubtype(typeRegistry.getNativeType(OBJECT_TYPE))) {
compiler.report(
JSError.make(sourceName, objectLit, LENDS_ON_NON_OBJECT,
lendsName, type.toString()));
type = null;
} else {
objectLit.setJSType(type);
}
}
}
info = NodeUtil.getBestJSDocInfo(objectLit);
Node lValue = NodeUtil.getBestLValue(objectLit);
String lValueName = NodeUtil.getBestLValueName(lValue);
boolean createdEnumType = false;
if (info != null && info.hasEnumParameterType()) {
type = createEnumTypeFromNodes(objectLit, lValueName, info, lValue);
createdEnumType = true;
}
if (type == null) {
type = typeRegistry.createAnonymousObjectType();
}
setDeferredType(objectLit, type);
// If this is an enum, the properties were already taken care of above.
processObjectLitProperties(
objectLit, ObjectType.cast(objectLit.getJSType()), !createdEnumType);
}
/**
* Process an object literal and all the types on it.
* @param objLit The OBJECTLIT node.
* @param objLitType The type of the OBJECTLIT node. This might be a named
* type, because of the lends annotation.
* @param declareOnOwner If true, declare properties on the objLitType as
* well. If false, the caller should take care of this.
*/
void processObjectLitProperties(
Node objLit,
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> ObjectType objLitType,
boolean declareOnOwner) {
for (Node keyNode = objLit.getFirstChild(); keyNode != null;
keyNode = keyNode.getNext()) {
Node value = keyNode.getFirstChild();
String memberName = NodeUtil.getObjectLitKeyName(keyNode);
JSDocInfo info = keyNode.getJSDocInfo();
JSType valueType =
getDeclaredType(keyNode.getSourceFileName(), info, keyNode, value);
JSType keyType = objLitType.isEnumType() ?
objLitType.toMaybeEnumType().getElementsType() :
NodeUtil.getObjectLitKeyTypeFromValueType(keyNode, valueType);
// Try to declare this property in the current scope if it
// has an authoritative name.
String qualifiedName = NodeUtil.getBestLValueName(keyNode);
if (qualifiedName != null) {
boolean inferred = keyType == null;
defineSlot(keyNode, objLit, qualifiedName, keyType, inferred);
} else if (keyType != null) {
setDeferredType(keyNode, keyType);
}
if (keyType != null && objLitType != null && declareOnOwner) {
// Declare this property on its object literal.
boolean isExtern = keyNode.isFromExterns();
objLitType.defineDeclaredProperty(memberName, keyType, keyNode);
}
}
}
/**
* Returns the type specified in a JSDoc annotation near a GETPROP or NAME.
*
* Extracts type information from either the {@code @type} tag or from
* the {@code @return} and {@code @param} tags.
*/
private JSType getDeclaredTypeInAnnotation(String sourceName,
Node node, JSDocInfo info) {
JSType jsType = null;
Node objNode =
node.isGetProp() ? node.getFirstChild() :
NodeUtil.isObjectLitKey(node, node.getParent()) ? node.getParent() :
null;
if (info != null) {
if (info.hasType()) {
jsType = info.getType().evaluate(scope, typeRegistry);
} else if (FunctionTypeBuilder.isFunctionTypeDeclaration(info)) {
String fnName = node.getQualifiedName();
jsType = createFunctionTypeFromNodes(
null, fnName, info, node);
}
}
return jsType;
}
/**
* Asserts that it's OK to define this node's name.
* The node should have a source name and be of the specified type.
*/
void assertDefinitionNode(Node n, int type) {
Preconditions.checkState(sourceName != null);
Preconditions.checkState(n.getType() == type);
}
/**
* Defines a catch parameter.
*/
void defineCatch(Node n, Node parent) {
assertDefinitionNode(n, Token.CATCH);
Node catchName = n.
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>getFirstChild();
defineSlot(catchName, n, null);
}
/**
* Defines a VAR initialization.
*/
void defineVar(Node n, Node parent) {
assertDefinitionNode(n, Token.VAR);
JSDocInfo info = n.getJSDocInfo();
if (n.hasMoreThanOneChild()) {
if (info != null) {
// multiple children
compiler.report(JSError.make(sourceName, n, MULTIPLE_VAR_DEF));
}
for (Node name : n.children()) {
defineName(name, n, parent, name.getJSDocInfo());
}
} else {
Node name = n.getFirstChild();
defineName(name, n, parent,
(info != null) ? info : name.getJSDocInfo());
}
}
/**
* Defines a function literal.
*/
void defineFunctionLiteral(Node n, Node parent) {
assertDefinitionNode(n, Token.FUNCTION);
// Determine the name and JSDocInfo and l-value for the function.
// Any of these may be null.
Node lValue = NodeUtil.getBestLValue(n);
JSDocInfo info = NodeUtil.getBestJSDocInfo(n);
String functionName = NodeUtil.getBestLValueName(lValue);
FunctionType functionType =
createFunctionTypeFromNodes(n, functionName, info, lValue);
// Assigning the function type to the function node
setDeferredType(n, functionType);
// Declare this symbol in the current scope iff it's a function
// declaration. Otherwise, the declaration will happen in other
// code paths.
if (NodeUtil.isFunctionDeclaration(n)) {
defineSlot(n.getFirstChild(), n, functionType);
}
}
/**
* Defines a variable based on the {@link Token#NAME} node passed.
* @param name The {@link Token#NAME} node.
* @param var The parent of the {@code name} node, which must be a
* {@link Token#VAR} node.
* @param parent {@code var}'s parent.
* @param info the {@link JSDocInfo} information relating to this
* {@code name} node.
*/
private void defineName(Node name, Node var, Node parent, JSDocInfo info) {
Node value = name.getFirstChild();
// variable's type
JSType type = getDeclaredType(sourceName, info, name, value);
if (type == null) {
// The variable's type will be inferred.
type = name.isFromExterns() ?
getNativeType(UNKNOWN_TYPE) : null;
}
defineSlot(name, var, type);
}
/**
* If a variable is assigned a function literal in the global scope,
* make that a declared type (even if there's no doc info).
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> * There's only one exception to this rule:
* if the return type is inferred, and we're in a local
* scope, we should assume the whole function is inferred.
*/
private boolean shouldUseFunctionLiteralType(
FunctionType type, JSDocInfo info, Node lValue) {
if (info != null) {
return true;
}
if (lValue != null &&
NodeUtil.isObjectLitKey(lValue, lValue.getParent())) {
return false;
}
return scope.isGlobal() || !type.isReturnTypeInferred();
}
/**
* Creates a new function type, based on the given nodes.
*
* This handles two cases that are semantically very different, but
* are not mutually exclusive:
* - A function literal that needs a type attached to it.
* - An assignment expression with function-type info in the JsDoc.
*
* All parameters are optional, and we will do the best we can to create
* a function type.
*
* This function will always create a function type, so only call it if
* you're sure that's what you want.
*
* @param rValue The function node.
* @param name the function's name
* @param info the {@link JSDocInfo} attached to the function definition
* @param lvalueNode The node where this function is being
* assigned. For example, {@code A.prototype.foo = ...} would be used to
* determine that this function is a method of A.prototype. May be
* null to indicate that this is not being assigned to a qualified name.
*/
private FunctionType createFunctionTypeFromNodes(
@Nullable Node rValue,
@Nullable String name,
@Nullable JSDocInfo info,
@Nullable Node lvalueNode) {
FunctionType functionType = null;
// Global ctor aliases should be registered with the type registry.
if (rValue != null && rValue.isQualifiedName() && scope.isGlobal()) {
Var var = scope.getVar(rValue.getQualifiedName());
if (var != null && var.getType() != null &&
var.getType().isFunctionType()) {
FunctionType aliasedType = var.getType().toMaybeFunctionType();
if ((aliasedType.isConstructor() || aliasedType.isInterface()) &&
!aliasedType.isNativeObjectType()) {
functionType = aliasedType;
if (name != null && scope.isGlobal()) {
typeRegistry.declareType(name, functionType.getInstanceType());
}
}
}
}
if (functionType == null) {
Node errorRoot = rValue == null ? lvalueNode : rValue;
boolean isFnLiteral =
rValue != null && rValue.isFunction();
Node fnRoot = isFnLiteral ? rValue : null;
Node parametersNode = isFnLiteral ?
rValue.getFirstChild().getNext() : null;
Node
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> fnBlock = isFnLiteral ? parametersNode.getNext() : null;
if (info != null && info.hasType()) {
JSType type = info.getType().evaluate(scope, typeRegistry);
// Known to be not null since we have the FUNCTION token there.
type = type.restrictByNotNullOrUndefined();
if (type.isFunctionType()) {
functionType = type.toMaybeFunctionType();
functionType.setJSDocInfo(info);
}
}
if (functionType == null) {
// Find the type of any overridden function.
Node ownerNode = NodeUtil.getBestLValueOwner(lvalueNode);
String ownerName = NodeUtil.getBestLValueName(ownerNode);
Var ownerVar = null;
String propName = null;
ObjectType ownerType = null;
if (ownerName != null) {
ownerVar = scope.getVar(ownerName);
if (ownerVar != null) {
ownerType = ObjectType.cast(ownerVar.getType());
}
if (name != null) {
propName = name.substring(ownerName.length() + 1);
}
}
FunctionType overriddenPropType = null;
if (ownerType != null && propName != null) {
overriddenPropType =
findOverriddenFunction(ownerType, propName);
}
FunctionTypeBuilder builder =
new FunctionTypeBuilder(name, compiler, errorRoot, sourceName,
scope)
.setContents(getFunctionAnalysisResults(fnRoot))
.inferFromOverriddenFunction(overriddenPropType, parametersNode)
.inferTemplateTypeName(info)
.inferReturnType(info)
.inferInheritance(info);
// Infer the context type.
boolean searchedForThisType = false;
if (ownerType != null && ownerType.isFunctionPrototypeType()) {
builder.inferThisType(
info, ownerType.getOwnerFunction().getInstanceType());
searchedForThisType = true;
} else if (ownerNode != null && ownerNode.isThis()) {
// If 'this' has a type, use that instead.
// This is a hack, necessary because CollectProperties (below)
// doesn't run with the scope that it's building,
// so scope.getTypeOfThis() will be wrong.
JSType injectedThisType = ownerNode.getJSType();
builder.inferThisType(
info,
injectedThisType == null ?
scope.getTypeOfThis() : injectedThisType);
searchedForThisType = true;
}
if (!searchedForThisType) {
builder.inferThisType(info);
}
functionType = builder
.inferParameterTypes(parametersNode, info)
.buildAndRegister();
}
}
// all done
return functionType;
}
/**
* Find the function that's being overridden on this type, if any.
*/
private FunctionType findOverriddenFunction(
ObjectType ownerType, String propName) {
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>
// First, check to see if the property is implemented
// on a superclass.
JSType propType = ownerType.getPropertyType(propName);
if (propType != null && propType.isFunctionType()) {
return propType.toMaybeFunctionType();
} else {
// If it's not, then check to see if it's implemented
// on an implemented interface.
for (ObjectType iface :
ownerType.getCtorImplementedInterfaces()) {
propType = iface.getPropertyType(propName);
if (propType != null && propType.isFunctionType()) {
return propType.toMaybeFunctionType();
}
}
}
return null;
}
/**
* Creates a new enum type, based on the given nodes.
*
* This handles two cases that are semantically very different, but
* are not mutually exclusive:
* - An object literal that needs an enum type attached to it.
* - An assignment expression with an enum tag in the JsDoc.
*
* This function will always create an enum type, so only call it if
* you're sure that's what you want.
*
* @param rValue The node of the enum.
* @param name The enum's name
* @param info The {@link JSDocInfo} attached to the enum definition.
* @param lValueNode The node where this function is being
* assigned.
*/
private EnumType createEnumTypeFromNodes(Node rValue, String name,
JSDocInfo info, Node lValueNode) {
Preconditions.checkNotNull(info);
Preconditions.checkState(info.hasEnumParameterType());
EnumType enumType = null;
if (rValue != null && rValue.isQualifiedName()) {
// Handle an aliased enum.
Var var = scope.getVar(rValue.getQualifiedName());
if (var != null && var.getType() instanceof EnumType) {
enumType = (EnumType) var.getType();
}
}
if (enumType == null) {
JSType elementsType =
info.getEnumParameterType().evaluate(scope, typeRegistry);
enumType = typeRegistry.createEnumType(name, rValue, elementsType);
if (rValue != null && rValue.isObjectLit()) {
// collect enum elements
Node key = rValue.getFirstChild();
while (key != null) {
String keyName = NodeUtil.getStringValue(key);
if (keyName == null) {
// GET and SET don't have a String value;
compiler.report(
JSError.make(sourceName, key, ENUM_NOT_CONSTANT, keyName));
} else if (!codingConvention.isValidEnumKey(keyName)) {
compiler.report(
JSError.make(sourceName, key, ENUM_NOT_CONSTANT, keyName));
} else {
enumType.defineElement(keyName, key);
}
key = key.getNext();
}
}
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>
}
if (name != null && scope.isGlobal()) {
typeRegistry.declareType(name, enumType.getElementsType());
}
return enumType;
}
/**
* Defines a typed variable. The defining node will be annotated with the
* variable's type or {@code null} if its type is inferred.
* @param name the defining node. It must be a {@link Token#NAME}.
* @param parent the {@code name}'s parent.
* @param type the variable's type. It may be {@code null}, in which case
* the variable's type will be inferred.
*/
private void defineSlot(Node name, Node parent, JSType type) {
defineSlot(name, parent, type, type == null);
}
/**
* Defines a typed variable. The defining node will be annotated with the
* variable's type of {@link JSTypeNative#UNKNOWN_TYPE} if its type is
* inferred.
*
* Slots may be any variable or any qualified name in the global scope.
*
* @param n the defining NAME or GETPROP node.
* @param parent the {@code n}'s parent.
* @param type the variable's type. It may be {@code null} if
* {@code inferred} is {@code true}.
*/
void defineSlot(Node n, Node parent, JSType type, boolean inferred) {
Preconditions.checkArgument(inferred || type != null);
// Only allow declarations of NAMEs and qualified names.
// Object literal keys will have to compute their names themselves.
if (n.isName()) {
Preconditions.checkArgument(
parent.isFunction() ||
parent.isVar() ||
parent.isParamList() ||
parent.isCatch());
} else {
Preconditions.checkArgument(
n.isGetProp() &&
(parent.isAssign() ||
parent.isExprResult()));
}
defineSlot(n, parent, n.getQualifiedName(), type, inferred);
}
/**
* Defines a symbol in the current scope.
*
* @param n the defining NAME or GETPROP or object literal key node.
* @param parent the {@code n}'s parent.
* @param variableName The name that this should be known by.
* @param type the variable's type. It may be {@code null} if
* {@code inferred} is {@code true}.
* @param inferred Whether the type is inferred or declared.
*/
void defineSlot(Node n, Node parent, String variableName,
JSType type, boolean inferred) {
Preconditions.checkArgument(!variableName.isEmpty());
boolean isGlobalVar = n.isName() && scope.isGlobal();
boolean shouldDeclareOnGlobalThis =
isGlobalVar &&
(parent.isVar() ||
parent.isFunction());
// If n is a property, then we should really declare it in the
// scope where the root object appears. This helps
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> out people
// who declare "global" names in an anonymous namespace.
Scope scopeToDeclareIn = scope;
if (n.isGetProp() && !scope.isGlobal() &&
isQnameRootedInGlobalScope(n)) {
Scope globalScope = scope.getGlobalScope();
// don't try to declare in the global scope if there's
// already a symbol there with this name.
if (!globalScope.isDeclared(variableName, false)) {
scopeToDeclareIn = scope.getGlobalScope();
}
}
// The input may be null if we are working with a AST snippet. So read
// the extern info from the node.
boolean isExtern = n.isFromExterns();
Var newVar = null;
// declared in closest scope?
CompilerInput input = compiler.getInput(inputId);
if (scopeToDeclareIn.isDeclared(variableName, false)) {
Var oldVar = scopeToDeclareIn.getVar(variableName);
newVar = validator.expectUndeclaredVariable(
sourceName, input, n, parent, oldVar, variableName, type);
} else {
if (!inferred) {
setDeferredType(n, type);
}
newVar =
scopeToDeclareIn.declare(variableName, n, type, input, inferred);
if (type instanceof EnumType) {
Node initialValue = newVar.getInitialValue();
boolean isValidValue = initialValue != null &&
(initialValue.isObjectLit() ||
initialValue.isQualifiedName());
if (!isValidValue) {
compiler.report(JSError.make(sourceName, n, ENUM_INITIALIZER));
}
}
}
// We need to do some additional work for constructors and interfaces.
FunctionType fnType = JSType.toMaybeFunctionType(type);
if (fnType != null &&
// We don't want to look at empty function types.
!type.isEmptyType()) {
if ((fnType.isConstructor() || fnType.isInterface()) &&
!fnType.equals(getNativeType(U2U_CONSTRUCTOR_TYPE))) {
// Declare var.prototype in the scope chain.
FunctionType superClassCtor = fnType.getSuperClassConstructor();
ObjectType.Property prototypeSlot = fnType.getSlot("prototype");
// When we declare the function prototype implicitly, we
// want to make sure that the function and its prototype
// are declared at the same node. We also want to make sure
// that the if a symbol has both a Var and a JSType, they have
// the same node.
//
// This consistency is helpful to users of SymbolTable,
// because everything gets declared at the same place.
prototypeSlot.setNode(n);
String prototypeName = variableName + ".prototype";
// There are some rare cases where the prototype will already
// be declared. See TypedScopeCreatorTest#testBogusPrototypeInit.
// Fortunately, other
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> warnings will complain if this happens.
Var prototypeVar = scopeToDeclareIn.getVar(prototypeName);
if (prototypeVar != null && prototypeVar.scope == scopeToDeclareIn) {
scopeToDeclareIn.undeclare(prototypeVar);
}
scopeToDeclareIn.declare(prototypeName,
n, prototypeSlot.getType(), input,
/* declared iff there's an explicit supertype */
superClassCtor == null ||
superClassCtor.getInstanceType().equals(
getNativeType(OBJECT_TYPE)));
// Make sure the variable is initialized to something if
// it constructs itself.
if (newVar.getInitialValue() == null &&
!isExtern &&
// We want to make sure that when we declare a new instance
// type (with @constructor) that there's actually a ctor for it.
// This doesn't apply to structural constructors
// (like function(new:Array). Checking the constructed
// type against the variable name is a sufficient check for
// this.
variableName.equals(
fnType.getInstanceType().getReferenceName())) {
compiler.report(
JSError.make(sourceName, n,
fnType.isConstructor() ?
CTOR_INITIALIZER : IFACE_INITIALIZER,
variableName));
}
}
}
if (shouldDeclareOnGlobalThis) {
ObjectType globalThis =
typeRegistry.getNativeObjectType(GLOBAL_THIS);
if (inferred) {
globalThis.defineInferredProperty(variableName,
type == null ?
getNativeType(JSTypeNative.NO_TYPE) :
type,
n);
} else {
globalThis.defineDeclaredProperty(variableName, type, n);
}
}
if (isGlobalVar && "Window".equals(variableName)
&& type != null
&& type.isFunctionType()
&& type.isConstructor()) {
FunctionType globalThisCtor =
typeRegistry.getNativeObjectType(GLOBAL_THIS).getConstructor();
globalThisCtor.getInstanceType().clearCachedValues();
globalThisCtor.getPrototype().clearCachedValues();
globalThisCtor
.setPrototypeBasedOn((type.toMaybeFunctionType()).getInstanceType());
}
}
/**
* Check if the given node is a property of a name in the global scope.
*/
private boolean isQnameRootedInGlobalScope(Node n) {
Scope scope = getQnameRootScope(n);
return scope != null && scope.isGlobal();
}
/**
* Return the scope for the name of the given node.
*/
private Scope getQnameRootScope(Node n) {
Node root = NodeUtil.getRootOfQualifiedName(n);
if (root.isName()) {
Var var = scope.getVar(root.getString());
if (var != null) {
return var.getScope();
}
}
return null;
}
/**
* Look for a type declaration on a property assignment
* (in an ASSIGN
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> or an object literal key).
*
* @param info The doc info for this property.
* @param lValue The l-value node.
* @param rValue The node that {@code n} is being initialized to,
* or {@code null} if this is a stub declaration.
*/
private JSType getDeclaredType(String sourceName, JSDocInfo info,
Node lValue, @Nullable Node rValue) {
if (info != null && info.hasType()) {
return getDeclaredTypeInAnnotation(sourceName, lValue, info);
} else if (rValue != null && rValue.isFunction() &&
shouldUseFunctionLiteralType(
&& firstClause.getString().equals(lValue.getString());
if (namesMatch && secondClause.getJSType() != null
&& !secondClause.getJSType().isUnknownType()) {
return secondClause.getJSType();
}
}
}
}
}
}
return getDeclaredTypeInAnnotation(sourceName, lValue, info);
}
private FunctionType getFunctionType(@Nullable Var v) {
JSType t = v == null ? null : v.getType();
ObjectType o = t == null ? null : t.dereference();
return JSType.toMaybeFunctionType(o);
}
/**
* Look for calls that set a delegate method's calling convention.
*/
private void checkForCallingConventionDefiningCalls(
Node n, Map<String, String> delegateCallingConventions) {
codingConvention.checkForCallingConventionDefiningCalls(n,
delegateCallingConventions);
}
/**
* Look for class-defining calls.
* Because JS has no 'native' syntax for defining classes,
* this is often very coding-convention dependent and business-logic heavy.
*/
private void checkForClassDefiningCalls(
NodeTraversal t, Node n, Node parent) {
SubclassRelationship relationship =
codingConvention.getClassesDefinedByCall(n);
if (relationship != null) {
FunctionType superCtor = getFunctionType(
scope.getVar(relationship.superclassName));
FunctionType subCtor = getFunctionType(
scope.getVar(relationship.subclassName));
if (superCtor != null && superCtor.isConstructor() &&
subCtor != null && subCtor.isConstructor()) {
ObjectType superClass = superCtor.getInstanceType();
ObjectType subClass = subCtor.getInstanceType();
// superCtor and subCtor might be structural constructors
// (like {function(new:Object)}) so we need to resolve them back
// to the original ctor objects.
superCtor = superClass.getConstructor();
subCtor = subClass.getConstructor();
if (relationship.type == SubclassType.INHERITS &&
!superClass.isEmptyType() && !subClass.isEmptyType()) {
validator.expectSuperType(t, n, superClass, subClass);
}
if (superCtor != null && subCtor != null)
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> {
codingConvention.applySubclassRelationship(
superCtor, subCtor, relationship.type);
}
}
}
String singletonGetterClassName =
codingConvention.getSingletonGetterClassName(n);
if (singletonGetterClassName != null) {
ObjectType objectType = ObjectType.cast(
typeRegistry.getType(singletonGetterClassName));
if (objectType != null) {
FunctionType functionType = objectType.getConstructor();
if (functionType != null) {
FunctionType getterType =
typeRegistry.createFunctionType(objectType);
codingConvention.applySingletonGetter(functionType, getterType,
objectType);
}
}
}
DelegateRelationship delegateRelationship =
codingConvention.getDelegateRelationship(n);
if (delegateRelationship != null) {
applyDelegateRelationship(delegateRelationship);
}
ObjectLiteralCast objectLiteralCast =
codingConvention.getObjectLiteralCast(n);
if (objectLiteralCast != null) {
if (objectLiteralCast.diagnosticType == null) {
ObjectType type = ObjectType.cast(
typeRegistry.getType(objectLiteralCast.typeName));
if (type != null && type.getConstructor() != null) {
setDeferredType(objectLiteralCast.objectNode, type);
} else {
compiler.report(JSError.make(t.getSourceName(), n,
CONSTRUCTOR_EXPECTED));
}
} else {
compiler.report(JSError.make(t.getSourceName(), n,
objectLiteralCast.diagnosticType));
}
}
}
/**
* Apply special properties that only apply to delegates.
*/
private void applyDelegateRelationship(
DelegateRelationship delegateRelationship) {
ObjectType delegatorObject = ObjectType.cast(
typeRegistry.getType(delegateRelationship.delegator));
ObjectType delegateBaseObject = ObjectType.cast(
typeRegistry.getType(delegateRelationship.delegateBase));
ObjectType delegateSuperObject = ObjectType.cast(
typeRegistry.getType(codingConvention.getDelegateSuperclassName()));
if (delegatorObject != null &&
delegateBaseObject != null &&
delegateSuperObject != null) {
FunctionType delegatorCtor = delegatorObject.getConstructor();
FunctionType delegateBaseCtor = delegateBaseObject.getConstructor();
FunctionType delegateSuperCtor = delegateSuperObject.getConstructor();
if (delegatorCtor != null && delegateBaseCtor != null &&
delegateSuperCtor != null) {
FunctionParamBuilder functionParamBuilder =
new FunctionParamBuilder(typeRegistry);
functionParamBuilder.addRequiredParams(
getNativeType(U2U_CONSTRUCTOR_TYPE));
FunctionType findDelegate = typeRegistry.createFunctionType(
typeRegistry.createDefaultObjectUnion(delegateBaseObject),
functionParamBuilder.build());
FunctionType delegateProxy = typeRegistry.createConstructorType(
delegateBaseObject.getReferenceName() + DELEGATE_PROXY_SUFFIX,
null, null, null);
delegateProxy.setPrototypeBasedOn(delegateBaseObject);
codingConvention.applyDelegateRelationship(
delegateSuper
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>Object, delegateBaseObject, delegatorObject,
delegateProxy, findDelegate);
delegateProxyPrototypes.add(delegateProxy.getPrototype());
}
}
}
/**
* Declare the symbol for a qualified name in the global scope.
*
* @param info The doc info for this property.
* @param n A top-level GETPROP node (it should not be contained inside
* another GETPROP).
* @param parent The parent of {@code n}.
* @param rhsValue The node that {@code n} is being initialized to,
* or {@code null} if this is a stub declaration.
*/
void maybeDeclareQualifiedName(NodeTraversal t, JSDocInfo info,
Node n, Node parent, Node rhsValue) {
Node ownerNode = n.getFirstChild();
String ownerName = ownerNode.getQualifiedName();
String qName = n.getQualifiedName();
String propName = n.getLastChild().getString();
Preconditions.checkArgument(qName != null && ownerName != null);
// Precedence of type information on GETPROPs:
// 1) @type annotation / @enum annotation
// 2) ASSIGN to FUNCTION literal
// 3) @param/@return annotation (with no function literal)
// 4) ASSIGN to something marked @const
// 5) ASSIGN to anything else
//
// 1, 3, and 4 are declarations, 5 is inferred, and 2 is a declaration iff
// the function has JsDoc or has not been declared before.
//
// FUNCTION literals are special because TypedScopeCreator is very smart
// about getting as much type information as possible for them.
// Determining type for #1 + #2 + #3 + #4
JSType valueType = getDeclaredType(t.getSourceName(), info, n, rhsValue);
if (valueType == null && rhsValue != null) {
// Determining type for #5
valueType = rhsValue.getJSType();
}
// Function prototypes are special.
// It's a common JS idiom to do:
// F.prototype = { ... };
// So if F does not have an explicitly declared super type,
// allow F.prototype to be redefined arbitrarily.
if ("prototype".equals(propName)) {
Var qVar = scope.getVar(qName);
if (qVar != null) {
// If the programmer has declared that F inherits from Super,
// and they assign F.prototype to an object literal,
// then they are responsible for making sure that the object literal's
// implicit prototype is set up appropriately. We just obey
// the @extends tag.
ObjectType qVarType = ObjectType.cast(qVar.getType());
if (qVarType != null &&
rhsValue != null &&
rhsValue.isObjectLit()) {
typeRegistry.resetImplicitPrototype(
rhsValue.getJSType(), qVarType.getImplicitPrototype
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>());
} else if (!qVar.isTypeInferred()) {
// If the programmer has declared that F inherits from Super,
// and they assign F.prototype to some arbitrary expression,
// there's not much we can do. We just ignore the expression,
// and hope they've annotated their code in a way to tell us
// what props are going to be on that prototype.
return;
}
if (qVar.getScope() == scope) {
scope.undeclare(qVar);
}
}
}
if (valueType == null) {
if (parent.isExprResult()) {
stubDeclarations.add(new StubDeclaration(
n,
t.getInput() != null && t.getInput().isExtern(),
ownerName));
}
return;
}
boolean inferred = isQualifiedNameInferred(
qName, n, info, rhsValue, valueType);
if (!inferred) {
ObjectType ownerType = getObjectSlot(ownerName);
if (ownerType != null) {
// Only declare this as an official property if it has not been
// declared yet.
boolean isExtern = t.getInput() != null && t.getInput().isExtern();
if ((!ownerType.hasOwnProperty(propName) ||
ownerType.isPropertyTypeInferred(propName)) &&
((isExtern && !ownerType.isNativeObjectType()) ||
!ownerType.isInstanceType())) {
// If the property is undeclared or inferred, declare it now.
ownerType.defineDeclaredProperty(propName, valueType, n);
}
}
// If the property is already declared, the error will be
// caught when we try to declare it in the current scope.
defineSlot(n, parent, valueType, inferred);
} else if (rhsValue != null && rhsValue.isTrue()) {
// We declare these for delegate proxy method properties.
FunctionType ownerType =
JSType.toMaybeFunctionType(getObjectSlot(ownerName));
if (ownerType != null) {
JSType ownerTypeOfThis = ownerType.getTypeOfThis();
String delegateName = codingConvention.getDelegateSuperclassName();
JSType delegateType = delegateName == null ?
null : typeRegistry.getType(delegateName);
if (delegateType != null &&
ownerTypeOfThis.isSubtype(delegateType)) {
defineSlot(n, parent, getNativeType(BOOLEAN_TYPE), true);
}
}
}
}
/**
* Determines whether a qualified name is inferred.
* NOTE(nicksantos): Determining whether a property is declared or not
* is really really obnoxious.
*
* The problem is that there are two (equally valid) coding styles:
*
* (function() {
* /* The authoritative definition of goog.bar. /
* goog.bar = function() {};
* })();
*
* function f() {
* goog.bar();
*
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> /* Reset goog.bar to a no-op. /
* goog.bar = function() {};
* }
*
* In a dynamic language with first-class functions, it's very difficult
* to know which one the user intended without looking at lots of
* contextual information (the second example demonstrates a small case
* of this, but there are some really pathological cases as well).
*
* The current algorithm checks if either the declaration has
* JsDoc type information, or @const with a known type,
* or a function literal with a name we haven't seen before.
*/
private boolean isQualifiedNameInferred(
String qName, Node n, JSDocInfo info,
Node rhsValue, JSType valueType) {
if (valueType == null) {
return true;
}
boolean inferred = true;
if (info != null) {
inferred = !(info.hasType()
|| info.hasEnumParameterType()
|| (info.isConstant() && valueType != null
&& !valueType.isUnknownType())
|| FunctionTypeBuilder.isFunctionTypeDeclaration(info));
}
if (inferred && rhsValue != null && rhsValue.isFunction()) {
if (info != null) {
return false;
} else if (!scope.isDeclared(qName, false) &&
n.isUnscopedQualifiedName()) {
// Check if this is in a conditional block.
// Functions assigned in conditional blocks are inferred.
for (Node current = n.getParent();
!(current.isScript() || current.isFunction());
current = current.getParent()) {
if (NodeUtil.isControlStructure(current)) {
return true;
}
}
// Check if this is assigned in an inner scope.
// Functions assigned in inner scopes are inferred.
AstFunctionContents contents =
getFunctionAnalysisResults(scope.getRootNode());
if (contents == null ||
!contents.getEscapedQualifiedNames().contains(qName)) {
return false;
}
}
}
return inferred;
}
/**
* Find the ObjectType associated with the given slot.
* @param slotName The name of the slot to find the type in.
* @return An object type, or null if this slot does not contain an object.
*/
private ObjectType getObjectSlot(String slotName) {
Var ownerVar = scope.getVar(slotName);
if (ownerVar != null) {
JSType ownerVarType = ownerVar.getType();
return ObjectType.cast(ownerVarType == null ?
null : ownerVarType.restrictByNotNullOrUndefined());
}
return null;
}
/**
* Resolve any stub declarations to unknown types if we could not
* find types for them during traversal.
*/
void resolveStubDeclarations() {
for (StubDeclaration stub : stubDeclarations) {
Node n = stub.node;
Node parent = n.getParent();
String qName = n.getQualifiedName();
String propName
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> = n.getLastChild().getString();
String ownerName = stub.ownerName;
boolean isExtern = stub.isExtern;
if (scope.isDeclared(qName, false)) {
continue;
}
// If we see a stub property, make sure to register this property
// in the type registry.
ObjectType ownerType = getObjectSlot(ownerName);
ObjectType unknownType = typeRegistry.getNativeObjectType(UNKNOWN_TYPE);
defineSlot(n, parent, unknownType, true);
if (ownerType != null &&
(isExtern || ownerType.isFunctionPrototypeType())) {
// If this is a stub for a prototype, just declare it
// as an unknown type. These are seen often in externs.
ownerType.defineInferredProperty(
propName, unknownType, n);
} else {
typeRegistry.registerPropertyOnType(
propName, ownerType == null ? unknownType : ownerType);
}
}
}
/**
* Collects all declared properties in a function, and
* resolves them relative to the global scope.
*/
private final class CollectProperties
extends AbstractShallowStatementCallback {
private final ObjectType thisType;
CollectProperties(ObjectType thisType) {
this.thisType = thisType;
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.isExprResult()) {
Node child = n.getFirstChild();
switch (child.getType()) {
case Token.ASSIGN:
maybeCollectMember(t, child.getFirstChild(), child,
child.getLastChild());
break;
case Token.GETPROP:
maybeCollectMember(t, child, child, null);
break;
}
}
}
private void maybeCollectMember(NodeTraversal t,
Node member, Node nodeWithJsDocInfo, @Nullable Node value) {
JSDocInfo info = nodeWithJsDocInfo.getJSDocInfo();
// Do nothing if there is no JSDoc type info, or
// if the node is not a member expression, or
// if the member expression is not of the form: this.someProperty.
if (info == null ||
!member.isGetProp() ||
!member.getFirstChild().isThis()) {
return;
}
member.getFirstChild().setJSType(thisType);
JSType jsType = getDeclaredType(t.getSourceName(), info, member, value);
Node name = member.getLastChild();
if (jsType != null &&
(name.isName() || name.isString())) {
thisType.defineDeclaredProperty(
name.getString(),
jsType,
member);
}
}
} // end CollectProperties
}
/**
* A stub declaration without any type information.
*/
private static final class StubDeclaration {
private final Node node;
private final boolean isExtern;
private final String ownerName;
private StubDeclaration(
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>Node node, boolean isExtern, String ownerName) {
this.node = node;
this.isExtern = isExtern;
this.ownerName = ownerName;
}
}
/**
* A shallow traversal of the global scope to build up all classes,
* functions, and methods.
*/
private final class GlobalScopeBuilder extends AbstractScopeBuilder {
private GlobalScopeBuilder(Scope scope) {
super(scope);
}
/**
* Visit a node in the global scope, and add anything it declares to the
* global symbol table.
*
* @param t The current traversal.
* @param n The node being visited.
* @param parent The parent of n
*/
@Override public void visit(NodeTraversal t, Node n, Node parent) {
super.visit(t, n, parent);
switch (n.getType()) {
case Token.VAR:
// Handle typedefs.
if (n.hasOneChild()) {
checkForTypedef(t, n.getFirstChild(), n.getJSDocInfo());
}
break;
}
}
@Override
void maybeDeclareQualifiedName(
NodeTraversal t, JSDocInfo info,
Node n, Node parent, Node rhsValue) {
checkForTypedef(t, n, info);
super.maybeDeclareQualifiedName(t, info, n, parent, rhsValue);
}
/**
* Handle typedefs.
* @param t The current traversal.
* @param candidate A qualified name node.
* @param info JSDoc comments.
*/
private void checkForTypedef(
NodeTraversal t, Node candidate, JSDocInfo info) {
if (info == null || !info.hasTypedefType()) {
return;
}
String typedef = candidate.getQualifiedName();
if (typedef == null) {
return;
}
// TODO(nicksantos|user): This is a terrible, terrible hack
// to bail out on recursive typedefs. We'll eventually need
// to handle these properly.
typeRegistry.declareType(typedef, getNativeType(UNKNOWN_TYPE));
JSType realType = info.getTypedefType().evaluate(scope, typeRegistry);
if (realType == null) {
compiler.report(
JSError.make(
t.getSourceName(), candidate, MALFORMED_TYPEDEF, typedef));
}
typeRegistry.overwriteDeclaredType(typedef, realType);
if (candidate.isGetProp()) {
defineSlot(candidate, candidate.getParent(),
getNativeType(NO_TYPE), false);
}
}
} // end GlobalScopeBuilder
/**
* A shallow traversal of a local scope to find all arguments and
* local variables.
*/
private final class LocalScopeBuilder extends AbstractScopeBuilder {
/**
* @param scope The scope that we're building.
*/
private LocalScopeBuilder(Scope scope) {
super(scope);
}
/**
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>
* Traverse the scope root and build it.
*/
void build() {
NodeTraversal.traverse(compiler, scope.getRootNode(), this);
AstFunctionContents contents =
getFunctionAnalysisResults(scope.getRootNode());
if (contents != null) {
for (String varName : contents.getEscapedVarNames()) {
Var v = scope.getVar(varName);
Preconditions.checkState(v.getScope() == scope);
v.markEscaped();
}
}
}
/**
* Visit a node in a local scope, and add any local variables or catch
* parameters into the local symbol table.
*
* @param t The node traversal.
* @param n The node being visited.
* @param parent The parent of n
*/
@Override public void visit(NodeTraversal t, Node n, Node parent) {
if (n == scope.getRootNode()) return;
if (n.isParamList() && parent == scope.getRootNode()) {
handleFunctionInputs(parent);
return;
}
super.visit(t, n, parent);
}
/** Handle bleeding functions and function parameters. */
private void handleFunctionInputs(Node fnNode) {
// Handle bleeding functions.
Node fnNameNode = fnNode.getFirstChild();
String fnName = fnNameNode.getString();
if (!fnName.isEmpty()) {
Scope.Var fnVar = scope.getVar(fnName);
if (fnVar == null ||
// Make sure we're not touching a native function. Native
// functions aren't bleeding, but may not have a declaration
// node.
(fnVar.getNameNode() != null &&
// Make sure that the function is actually bleeding by checking
// if has already been declared.
fnVar.getInitialValue() != fnNode)) {
defineSlot(fnNameNode, fnNode, fnNode.getJSType(), false);
}
}
declareArguments(fnNode);
}
/**
* Declares all of a function's arguments.
*/
private void declareArguments(Node functionNode) {
Node astParameters = functionNode.getFirstChild().getNext();
Node body = astParameters.getNext();
FunctionType functionType =
JSType.toMaybeFunctionType(functionNode.getJSType());
if (functionType != null) {
Node jsDocParameters = functionType.getParametersNode();
if (jsDocParameters != null) {
Node jsDocParameter = jsDocParameters.getFirstChild();
for (Node astParameter : astParameters.children()) {
if (jsDocParameter != null) {
defineSlot(astParameter, functionNode,
jsDocParameter.getJSType(), false);
jsDocParameter = jsDocParameter.getNext();
} else {
defineSlot(astParameter, functionNode, null, true);
}
}
}
}
} // end declareArguments
} // end LocalScopeBuilder
/**
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>
* Does a first-order function analysis that just looks at simple things
* like what variables are escaped, and whether 'this' is used.
*/
private static class FirstOrderFunctionAnalyzer
extends AbstractScopedCallback implements CompilerPass {
private final AbstractCompiler compiler;
private final Map<Node, AstFunctionContents> data;
FirstOrderFunctionAnalyzer(
AbstractCompiler compiler, Map<Node, AstFunctionContents> outParam) {
this.compiler = compiler;
this.data = outParam;
}
@Override public void process(Node externs, Node root) {
if (externs == null) {
NodeTraversal.traverse(compiler, root, this);
} else {
NodeTraversal.traverseRoots(
compiler, ImmutableList.of(externs, root), this);
}
}
@Override public void enterScope(NodeTraversal t) {
if (!t.inGlobalScope()) {
Node n = t.getScopeRoot();
data.put(n, new AstFunctionContents(n));
}
}
@Override public void visit(NodeTraversal t, Node n, Node parent) {
if (t.inGlobalScope()) {
return;
}
if (n.isReturn() && n.getFirstChild() != null) {
data.get(t.getScopeRoot()).recordNonEmptyReturn();
}
if (t.getScopeDepth() <= 2) {
// We only need to worry about escaped variables at depth 3.
// An variable escaped at depth 2 is, by definition, a global variable.
// We treat all global variables as escaped by default, so there's
// no reason to do this extra computation for them.
return;
}
if (n.isName() && NodeUtil.isLValue(n)) {
String name = n.getString();
Scope scope = t.getScope();
Var var = scope.getVar(name);
if (var != null) {
Scope ownerScope = var.getScope();
if (scope != ownerScope && ownerScope.isLocal()) {
data.get(ownerScope.getRootNode()).recordEscapedVarName(name);
}
}
} else if (n.isGetProp() && n.isUnscopedQualifiedName() &&
NodeUtil.isLValue(n)) {
String name = NodeUtil.getRootOfQualifiedName(n).getString();
Scope scope = t.getScope();
Var var = scope.getVar(name);
if (var != null) {
Scope ownerScope = var.getScope();
if (scope != ownerScope && ownerScope.isLocal()) {
data.get(ownerScope.getRootNode())
.recordEscapedQualifiedName(n.getQualifiedName());
}
}
}
}
}
private AstFunctionContents getFunctionAnalysisResults(@Nullable Node n) {
if (n == null) {
return null;
}
// Sometimes this will return null in things like
// NameReferenceGraphConstruction
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>/*
* Copyright 2004 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Preconditions;
import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import java.util.*;
/**
* Verifies that constants are only assigned a value once.
* e.g. var XX = 5;
* XX = 3; // error!
* XX++; // error!
*
*/
class ConstCheck extends AbstractPostOrderCallback
implements CompilerPass {
static final DiagnosticType CONST_REASSIGNED_VALUE_ERROR =
DiagnosticType.error(
"JSC_CONSTANT_REASSIGNED_VALUE_ERROR",
"constant {0} assigned a value more than once");
private final AbstractCompiler compiler;
private final Set<Scope.Var> initializedConstants;
/**
* Creates an instance.
*/
public ConstCheck(AbstractCompiler compiler) {
this.compiler = compiler;
this.initializedConstants = new HashSet<Scope.Var>();
}
@Override
public void process(Node externs, Node root) {
Preconditions.checkState(compiler.getLifeCycleStage().isNormalized());
NodeTraversal.traverse(compiler, root, this);
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getType()) {
case Token.NAME:
if (parent != null &&
parent.isVar() &&
n.hasChildren()) {
String name = n.getString();
Scope.Var var = t.getScope().getVar(name);
if (isConstant(var)) {
if (initializedConstants.contains(var)) {
reportError(t, n, name);
} else {
initializedConstants.add(var);
}
}
}
break;
case Token.ASSIGN:
case Token.ASSIGN_BITOR:
case Token.ASSIGN_BITXOR:
case Token.ASSIGN_BITAND:
case Token.ASSIGN_LSH:
case Token.ASSIGN_RSH:
case Token.ASSIGN_URSH:
case Token.ASSIGN_ADD:
case Token.ASSIGN_SUB:
case Token.ASSIGN_MUL
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> {
return new GlobalVarRefCleanupPass(compiler);
}
};
/**
* A CleanupPass implementation that will remove stored scopes from the
* MemoizedScopeCreator of the compiler instance for a the hot swapped script.
* <p>
* This pass will also clear out Source Nodes of Function Types declared on
* Vars tracked by MemoizedScopeCreator
*/
static class MemoizedScopeCleanupPass implements HotSwapCompilerPass {
private final AbstractCompiler compiler;
public MemoizedScopeCleanupPass(AbstractCompiler compiler) {
this.compiler = compiler;
}
@Override
public void hotSwapScript(Node scriptRoot, Node originalRoot) {
ScopeCreator creator = compiler.getTypedScopeCreator();
if (creator instanceof MemoizedScopeCreator) {
MemoizedScopeCreator scopeCreator = (MemoizedScopeCreator) creator;
String newSrc = scriptRoot.getSourceFileName();
for (Var var : scopeCreator.getAllSymbols()) {
JSType type = var.getType();
if (type != null) {
FunctionType fnType = type.toMaybeFunctionType();
if (fnType != null
&& newSrc.equals(NodeUtil.getSourceName(fnType.getSource()))) {
fnType.setSource(null);
}
}
}
scopeCreator.removeScopesForScript(originalRoot.getSourceFileName());
}
}
@Override
public void process(Node externs, Node root) {
// MemoizedScopeCleanupPass should not do work during process.
}
}
}
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>Exported(name, local);
}
@Override
public final boolean isExported(String name) {
return isExported(name, false) || isExported(name, true);
}
@Override
public boolean isPrivate(String name) {
return nextConvention.isPrivate(name);
}
@Override
public SubclassRelationship getClassesDefinedByCall(Node callNode) {
return nextConvention.getClassesDefinedByCall(callNode);
}
@Override
public boolean isSuperClassReference(String propertyName) {
return nextConvention.isSuperClassReference(propertyName);
}
@Override
public String extractClassNameIfProvide(Node node, Node parent) {
return nextConvention.extractClassNameIfProvide(node, parent);
}
@Override
public String extractClassNameIfRequire(Node node, Node parent) {
return nextConvention.extractClassNameIfRequire(node, parent);
}
@Override
public String getExportPropertyFunction() {
return nextConvention.getExportPropertyFunction();
}
@Override
public String getExportSymbolFunction() {
return nextConvention.getExportSymbolFunction();
}
@Override
public List<String> identifyTypeDeclarationCall(Node n) {
return nextConvention.identifyTypeDeclarationCall(n);
}
@Override
public void applySubclassRelationship(FunctionType parentCtor,
FunctionType childCtor, SubclassType type) {
nextConvention.applySubclassRelationship(
parentCtor, childCtor, type);
}
@Override
public String getAbstractMethodName() {
return nextConvention.getAbstractMethodName();
}
@Override
public String getSingletonGetterClassName(Node callNode) {
return nextConvention.getSingletonGetterClassName(callNode);
}
@Override
public void applySingletonGetter(FunctionType functionType,
FunctionType getterType, ObjectType objectType) {
nextConvention.applySingletonGetter(
functionType, getterType, objectType);
}
@Override
public boolean isInlinableFunction(Node n) {
return nextConvention.isInlinableFunction(n);
}
@Override
public DelegateRelationship getDelegateRelationship(Node callNode) {
return nextConvention.getDelegateRelationship(callNode);
}
@Override
public void applyDelegateRelationship(
ObjectType delegateSuperclass, ObjectType delegateBase,
ObjectType delegator, FunctionType delegateProxy,
FunctionType findDelegate) {
nextConvention.applyDelegateRelationship(
delegateSuperclass, delegateBase, delegator,
delegateProxy, findDelegate);
}
@Override
public String getDelegateSuperclassName() {
return nextConvention.getDelegateSuperclassName();
}
@Override
public void checkForCallingConventionDefiningCalls(
Node n, Map<String, String> delegateCallingConventions) {
nextConvention.checkForCallingConventionDefiningCalls(
n, delegateCallingConventions);
}
@Override
public void defineDelegateProxyPrototypeProperties(
JSTypeRegistry registry, StaticScope<JSType> scope,
List<ObjectType>
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> delegateProxyPrototypes,
Map<String, String> delegateCallingConventions) {
nextConvention.defineDelegateProxyPrototypeProperties(
registry, scope, delegateProxyPrototypes, delegateCallingConventions);
}
@Override
public String getGlobalObject() {
return nextConvention.getGlobalObject();
}
@Override
public Collection<AssertionFunctionSpec> getAssertionFunctions() {
return nextConvention.getAssertionFunctions();
}
@Override
public Bind describeFunctionBind(Node n) {
return describeFunctionBind(n, false);
}
@Override
public Bind describeFunctionBind(Node n, boolean useTypeInfo) {
return nextConvention.describeFunctionBind(n, useTypeInfo);
}
@Override
public boolean isPropertyTestFunction(Node call) {
return nextConvention.isPropertyTestFunction(call);
}
@Override
public boolean isPrototypeAlias(Node getProp) {
return false;
}
@Override
public ObjectLiteralCast getObjectLiteralCast(Node callNode) {
return nextConvention.getObjectLiteralCast(callNode);
}
@Override
public Collection<String> getIndirectlyDeclaredProperties() {
return nextConvention.getIndirectlyDeclaredProperties();
}
}
/**
* The default coding convention.
* Should be at the bottom of all proxy chains.
*/
private static class DefaultCodingConvention implements CodingConvention {
private static final long serialVersionUID = 1L;
@Override
public boolean isConstant(String variableName) {
return false;
}
@Override
public boolean isConstantKey(String variableName) {
return false;
}
@Override
public boolean isValidEnumKey(String key) {
return key != null && key.length() > 0;
}
@Override
public boolean isOptionalParameter(Node parameter) {
// be as lax as possible, but this must be mutually exclusive from
// var_args parameters.
return !isVarArgsParameter(parameter);
}
@Override
public boolean isVarArgsParameter(Node parameter) {
// be as lax as possible
return parameter.getParent().getLastChild() == parameter;
}
@Override
public boolean isExported(String name, boolean local) {
return local && name.startsWith("$super");
}
@Override
public boolean isExported(String name) {
return isExported(name, false) || isExported(name, true);
}
@Override
public boolean isPrivate(String name) {
return false;
}
@Override
public SubclassRelationship getClassesDefinedByCall(Node callNode) {
return null;
}
@Override
public boolean isSuperClassReference(String propertyName) {
return false;
}
@Override
public String extractClassNameIfProvide(Node node, Node parent) {
String message = "only implemented in GoogleCodingConvention";
throw new UnsupportedOperationException(message);
}
@Override
public String extractClassNameIfRequire(Node node, Node parent) {
String
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> message = "only implemented in GoogleCodingConvention";
throw new UnsupportedOperationException(message);
}
@Override
public String getExportPropertyFunction() {
return null;
}
@Override
public String getExportSymbolFunction() {
return null;
}
@Override
public List<String> identifyTypeDeclarationCall(Node n) {
return null;
}
@Override
public void applySubclassRelationship(FunctionType parentCtor,
FunctionType childCtor, SubclassType type) {
// do nothing
}
@Override
public String getAbstractMethodName() {
return null;
}
@Override
public String getSingletonGetterClassName(Node callNode) {
return null;
}
@Override
public void applySingletonGetter(FunctionType functionType,
FunctionType getterType, ObjectType objectType) {
// do nothing.
}
@Override
public boolean isInlinableFunction(Node n) {
Preconditions.checkState(n.isFunction());
return true;
}
@Override
public DelegateRelationship getDelegateRelationship(Node callNode) {
return null;
}
@Override
public void applyDelegateRelationship(
ObjectType delegateSuperclass, ObjectType delegateBase,
ObjectType delegator, FunctionType delegateProxy,
FunctionType findDelegate) {
// do nothing.
}
@Override
public String getDelegateSuperclassName() {
return null;
}
@Override
public void checkForCallingConventionDefiningCalls(Node n,
Map<String, String> delegateCallingConventions) {
// do nothing.
}
@Override
public void defineDelegateProxyPrototypeProperties(
JSTypeRegistry registry, StaticScope<JSType> scope,
List<ObjectType> delegateProxyPrototypes,
Map<String, String> delegateCallingConventions) {
// do nothing.
}
@Override
public String getGlobalObject() {
return "window";
}
@Override
public boolean isPropertyTestFunction(Node call) {
return false;
}
@Override
public boolean isPrototypeAlias(Node getProp) {
return false;
}
@Override
public ObjectLiteralCast getObjectLiteralCast(Node callNode) {
return null;
}
@Override
public Collection<AssertionFunctionSpec> getAssertionFunctions() {
return Collections.emptySet();
}
@Override
public Bind describeFunctionBind(Node n) {
return describeFunctionBind(n, false);
}
@Override
public Bind describeFunctionBind(Node n, boolean useTypeInfo) {
if (!n.isCall()) {
return null;
}
Node callTarget = n.getFirstChild();
String name = callTarget.getQualifiedName();
if (name != null) {
if (name.equals("Function.prototype.bind.call")) {
// goog.bind(fn, self, args...);
Node fn = callTarget.getNext();
if (fn == null) {
return null;
}
Node thisValue = safeNext(fn);
Node parameters = safe
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> originalRoot) {
NodeTraversal.traverse(compiler, scriptRoot, this);
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
// VOID nodes appear when there are extra semicolons at the BLOCK level.
// I've been unable to think of any cases where this indicates a bug,
// and apparently some people like keeping these semicolons around,
// so we'll allow it.
if (n.isEmpty() ||
n.isComma()) {
return;
}
if (parent == null) {
return;
}
// Do not try to remove a block or an expr result. We already handle
// these cases when we visit the child, and the peephole passes will
// fix up the tree in more clever ways when these are removed.
if (n.isExprResult() || n.isBlock()) {
return;
}
// This no-op statement was there so that JSDoc information could
// be attached to the name. This check should not complain about it.
if (n.isQualifiedName() && n.getJSDocInfo() != null) {
return;
}
boolean isResultUsed = NodeUtil.isExpressionResultUsed(n);
boolean isSimpleOp = NodeUtil.isSimpleOperatorType(n.getType());
if (!isResultUsed &&
(isSimpleOp || !NodeUtil.mayHaveSideEffects(n, t.getCompiler()))) {
String msg = "This code lacks side-effects. Is there a bug?";
if (n.isString()) {
msg = "Is there a missing '+' on the previous line?";
} else if (isSimpleOp) {
msg = "The result of the '" + Token.name(n.getType()).toLowerCase() +
"' operator is not being used.";
}
t.getCompiler().report(
t.makeError(n, level, USELESS_CODE_ERROR, msg));
// TODO(johnlenz): determine if it is necessary to
// try to protect side-effect free statements as well.
if (!NodeUtil.isStatement(n)) {
problemNodes.add(n);
}
}
}
/**
* Protect side-effect free nodes by making them parameters
* to a extern function call. This call will be removed
* after all the optimizations passes have run.
*/
private void protectSideEffects() {
if (!problemNodes.isEmpty()) {
addExtern();
for (Node n : problemNodes) {
Node name = IR.name(PROTECTOR_FN).srcref(n);
name.putBooleanProp(Node.IS_CONSTANT_NAME, true);
Node replacement = IR.call(name).srcref(n);
replacement.putBooleanProp(Node.FREE_CALL, true);
n.getParent().replaceChild(n, replacement);
replacement.addChildToBack(n);
}
compiler.reportCode
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>/*
* Copyright 2009 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.javascript.jscomp.Scope.Var;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.jstype.StaticSymbolTable;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* Memoize a scope creator.
*
* This allows you to make multiple passes, without worrying about
* the expense of generating Scope objects over and over again.
*
* On the other hand, you also have to be more aware of what your passes
* are doing. Scopes are memoized stupidly, so if the underlying tree
* changes, the scope may be out of sync.
*
* @author nicksantos@google.com (Nick Santos)
*/
class MemoizedScopeCreator
implements ScopeCreator, StaticSymbolTable<Var, Var> {
private final Map<Node, Scope> scopes = Maps.newHashMap();
private final ScopeCreator delegate;
/**
* @param delegate The real source of Scope objects.
*/
MemoizedScopeCreator(ScopeCreator delegate) {
this.delegate = delegate;
}
@Override
public Iterable<Var> getReferences(Var var) {
return ImmutableList.of(var);
}
@Override
public Scope getScope(Var var) {
return var.scope;
}
@Override
public Iterable<Var> getAllSymbols() {
List<Var> vars = Lists.newArrayList();
for (Scope s : scopes.values()) {
Iterables.addAll(vars, s.getAllSymbols());
}
return vars;
}
@Override
public Scope createScope(Node n, Scope parent) {
Scope scope = scopes.get(n);
if (scope == null) {
scope = delegate.createScope(n, parent);
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>
scopes.put(n, scope);
} else {
Preconditions.checkState(parent == scope.getParent());
}
return scope;
}
Collection<Scope> getAllMemoizedScopes() {
return Collections.unmodifiableCollection(scopes.values());
}
Scope getScopeIfMemoized(Node n) {
return scopes.get(n);
}
/**
* Removes all scopes with root nodes from a given script file.
*
* @param scriptName the name of the script file to remove nodes for.
*/
void removeScopesForScript(String scriptName) {
for (Node scopeRoot : ImmutableSet.copyOf(scopes.keySet())) {
if (scriptName.equals(scopeRoot.getSourceFileName())) {
scopes.remove(scopeRoot);
}
}
}
}
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> CompilerTestCase validation, so this
// parent pointer check serves as a more general check.
Preconditions.checkState(child.getParent() == n);
normalizeNodeTypes(child);
}
}
/**
* Add blocks to IF, WHILE, DO, etc.
*/
private void normalizeBlocks(Node n) {
if (NodeUtil.isControlStructure(n)
&& !n.isLabel()
&& !n.isSwitch()) {
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (NodeUtil.isControlStructureCodeBlock(n,c) &&
!c.isBlock()) {
Node newBlock = IR.block().srcref(n);
n.replaceChild(c, newBlock);
if (!c.isEmpty()) {
newBlock.addChildrenToFront(c);
} else {
newBlock.setWasEmptyNode(true);
}
c = newBlock;
reportChange();
}
}
}
}
/**
* Normalize where annotations appear on the AST. Copies
* around existing JSDoc annotations as well as internal annotations.
*/
static class PrepareAnnotations
implements NodeTraversal.Callback {
private final CodingConvention convention;
PrepareAnnotations(AbstractCompiler compiler) {
this.convention = compiler.getCodingConvention();
}
@Override
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.isObjectLit()) {
normalizeObjectLiteralAnnotations(n);
}
return true;
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getType()) {
case Token.CALL:
annotateCalls(n);
break;
case Token.FUNCTION:
annotateFunctions(n, parent);
annotateDispatchers(n, parent);
break;
}
}
private void normalizeObjectLiteralAnnotations(Node objlit) {
Preconditions.checkState(objlit.isObjectLit());
for (Node key = objlit.getFirstChild();
key != null; key = key.getNext()) {
Node value = key.getFirstChild();
normalizeObjectLiteralKeyAnnotations(objlit, key, value);
}
}
/**
* There are two types of calls we are interested in calls without explicit
* "this" values (what we are call "free" calls) and direct call to eval.
*/
private void annotateCalls(Node n) {
Preconditions.checkState(n.isCall());
// Keep track of of the "this" context of a call. A call without an
// explicit "this" is a free call.
Node first = n.getFirstChild();
if (!NodeUtil.isGet(first)) {
n.putBooleanProp(Node.FREE_CALL, true);
}
// Keep track of the context in which eval is called. It is important
// to distinguish between "(0, eval)()" and "eval
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>()".
if (first.isName() &&
"eval".equals(first.getString())) {
first.putBooleanProp(Node.DIRECT_EVAL, true);
}
}
/**
* Translate dispatcher info into the property expected node.
*/
private void annotateDispatchers(Node n, Node parent) {
Preconditions.checkState(n.isFunction());
if (parent.getJSDocInfo() != null
&& parent.getJSDocInfo().isJavaDispatch()) {
if (parent.isAssign()) {
Preconditions.checkState(parent.getLastChild() == n);
n.putBooleanProp(Node.IS_DISPATCHER, true);
}
}
}
/**
* In the AST that Rhino gives us, it needs to make a distinction
* between JsDoc on the object literal node and JsDoc on the object literal
* value. For example,
* <pre>
* var x = {
* / JSDOC /
* a: 'b',
* c: / JSDOC / 'd'
* };
* </pre>
*
* But in few narrow cases (in particular, function literals), it's
* a lot easier for us if the doc is attached to the value.
*/
private void normalizeObjectLiteralKeyAnnotations(
Node objlit, Node key, Node value) {
Preconditions.checkState(objlit.isObjectLit());
if (key.getJSDocInfo() != null &&
value.isFunction()) {
value.setJSDocInfo(key.getJSDocInfo());
}
}
/**
* Annotate optional and var_arg function parameters.
*/
private void annotateFunctions(Node n, Node parent) {
JSDocInfo fnInfo = NodeUtil.getFunctionJSDocInfo(n);
// Compute which function parameters are optional and
// which are var_args.
Node args = n.getFirstChild().getNext();
for (Node arg = args.getFirstChild();
arg != null;
arg = arg.getNext()) {
String argName = arg.getString();
JSTypeExpression typeExpr = fnInfo == null ?
null : fnInfo.getParameterType(argName);
if (convention.isOptionalParameter(arg) ||
typeExpr != null && typeExpr.isOptionalArg()) {
arg.putBooleanProp(Node.IS_OPTIONAL_PARAM, true);
}
if (convention.isVarArgsParameter(arg) ||
typeExpr != null && typeExpr.isVarArgs()) {
arg.putBooleanProp(Node.IS_VAR_ARGS_PARAM, true);
}
}
}
}
}
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> violations, so that
* type-based optimizations (like AmbiguateProperties) can be fault-tolerant.
*
* @author nicksantos@google.com (Nick Santos)
*/
class TypeValidator {
private final AbstractCompiler compiler;
private final JSTypeRegistry typeRegistry;
private final JSType allValueTypes;
private boolean shouldReport = true;
private final JSType nullOrUndefined;
// TODO(nicksantos): Provide accessors to better filter the list of type
// mismatches. For example, if we pass (Cake|null) where only Cake is
// allowed, that doesn't mean we should invalidate all Cakes.
private final List<TypeMismatch> mismatches = Lists.newArrayList();
// User warnings
private static final String FOUND_REQUIRED =
"{0}\n" +
"found : {1}\n" +
"required: {2}";
static final DiagnosticType INVALID_CAST =
DiagnosticType.warning("JSC_INVALID_CAST",
"invalid cast - must be a subtype or supertype\n" +
"from: {0}\n" +
"to : {1}");
static final DiagnosticType TYPE_MISMATCH_WARNING =
DiagnosticType.warning(
"JSC_TYPE_MISMATCH",
"{0}");
static final DiagnosticType MISSING_EXTENDS_TAG_WARNING =
DiagnosticType.warning(
"JSC_MISSING_EXTENDS_TAG",
"Missing @extends tag on type {0}");
static final DiagnosticType DUP_VAR_DECLARATION =
DiagnosticType.warning("JSC_DUP_VAR_DECLARATION",
"variable {0} redefined with type {1}, " +
"original definition at {2}:{3} with type {4}");
static final DiagnosticType HIDDEN_PROPERTY_MISMATCH =
DiagnosticType.warning("JSC_HIDDEN_PROPERTY_MISMATCH",
"mismatch of the {0} property type and the type " +
"of the property it overrides from superclass {1}\n" +
"original: {2}\n" +
"override: {3}");
static final DiagnosticType INTERFACE_METHOD_NOT_IMPLEMENTED =
DiagnosticType.warning(
"JSC_INTERFACE_METHOD_NOT_IMPLEMENTED",
"property {0} on interface {1} is not implemented by type {2}");
static final DiagnosticType HIDDEN_INTERFACE_PROPERTY_MISMATCH =
DiagnosticType.warning(
"JSC_HIDDEN_INTERFACE_PROPERTY_MISMATCH",
"mismatch of the {0} property type and the type " +
"of the property it overrides from interface {1}\n" +
"original: {2}\n" +
"override: {3}");
static final DiagnosticGroup ALL_DIAGNOSTICS = new DiagnosticGroup(
INVALID_CAST,
TYPE_MISMATCH_WARNING,
MISSING_EXTENDS_TAG_WARNING,
DUP_VAR_
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>DECLARATION,
HIDDEN_PROPERTY_MISMATCH,
INTERFACE_METHOD_NOT_IMPLEMENTED,
HIDDEN_INTERFACE_PROPERTY_MISMATCH);
TypeValidator(AbstractCompiler compiler) {
this.compiler = compiler;
this.typeRegistry = compiler.getTypeRegistry();
this.allValueTypes = typeRegistry.createUnionType(
STRING_TYPE, NUMBER_TYPE, BOOLEAN_TYPE, NULL_TYPE, VOID_TYPE);
this.nullOrUndefined = typeRegistry.createUnionType(
NULL_TYPE, VOID_TYPE);
}
/**
* Gets a list of type violations.
*
* For each violation, one element is the expected type and the other is
* the type that is actually found. Order is not significant.
*/
Iterable<TypeMismatch> getMismatches() {
return mismatches;
}
void setShouldReport(boolean report) {
this.shouldReport = report;
}
// All non-private methods should have the form:
// expectCondition(NodeTraversal t, Node n, ...);
// If there is a mismatch, the {@code expect} method should issue
// a warning and attempt to correct the mismatch, when possible.
/**
* Expect the type to be an object, or a type convertible to object. If the
* expectation is not met, issue a warning at the provided node's source code
* position.
* @return True if there was no warning, false if there was a mismatch.
*/
boolean expectObject(NodeTraversal t, Node n, JSType type, String msg) {
if (!type.matchesObjectContext()) {
mismatch(t, n, msg, type, OBJECT_TYPE);
return false;
}
return true;
}
/**
* Expect the type to be an object. Unlike expectObject, a type convertible
* to object is not acceptable.
*/
void expectActualObject(NodeTraversal t, Node n, JSType type, String msg) {
if (!type.isObject()) {
mismatch(t, n, msg, type, OBJECT_TYPE);
}
}
/**
* Expect the type to contain an object sometimes. If the expectation is
* not met, issue a warning at the provided node's source code position.
*/
void expectAnyObject(NodeTraversal t, Node n, JSType type, String msg) {
JSType anyObjectType = getNativeType(NO_OBJECT_TYPE);
if (!anyObjectType.isSubtype(type) && !type.isEmptyType()) {
mismatch(t, n, msg, type, anyObjectType);
}
}
/**
* Expect the type to be a string, or a type convertible to string. If the
* expectation is not met, issue a warning at the provided node's source code
* position.
*/
void expectString(NodeTraversal t, Node n, JSType type, String msg) {
if (!type.matchesStringContext()) {
mismatch(t, n, msg
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> private boolean containsForwardDeclaredUnresolvedName(JSType type) {
if (type.isUnionType()) {
for (JSType alt : type.toMaybeUnionType().getAlternates()) {
if (containsForwardDeclaredUnresolvedName(alt)) {
return true;
}
}
}
return type.isNoResolvedType();
}
/**
* Expect that the type of a switch condition matches the type of its
* case condition.
*/
void expectSwitchMatchesCase(NodeTraversal t, Node n, JSType switchType,
JSType caseType) {
// ECMA-262, page 68, step 3 of evaluation of CaseBlock,
// but allowing extra autoboxing.
// TODO(user): remove extra conditions when type annotations
// in the code base have adapted to the change in the compiler.
if (!switchType.canTestForShallowEqualityWith(caseType) &&
(caseType.autoboxesTo() == null ||
!caseType.autoboxesTo().isSubtype(switchType))) {
mismatch(t, n.getFirstChild(),
"case expression doesn't match switch",
caseType, switchType);
}
}
/**
* Expect that the first type can be addressed with GETELEM syntax,
* and that the second type is the right type for an index into the
* first type.
*
* @param t The node traversal.
* @param n The GETELEM node to issue warnings on.
* @param objType The type of the left side of the GETELEM.
* @param indexType The type inside the brackets of the GETELEM.
*/
void expectIndexMatch(NodeTraversal t, Node n, JSType objType,
JSType indexType) {
Preconditions.checkState(n.isGetElem());
Node indexNode = n.getLastChild();
if (objType.isUnknownType()) {
expectStringOrNumber(t, indexNode, indexType, "property access");
} else {
ObjectType dereferenced = objType.dereference();
if (dereferenced != null && dereferenced.getIndexType() != null) {
expectCanAssignTo(t, indexNode, indexType, dereferenced.getIndexType(),
"restricted index type");
} else if (dereferenced != null && dereferenced.isArrayType()) {
expectNumber(t, indexNode, indexType, "array access");
} else if (objType.matchesObjectContext()) {
expectString(t, indexNode, indexType, "property access");
} else {
mismatch(t, n, "only arrays or objects can be accessed",
objType,
typeRegistry.createUnionType(ARRAY_TYPE, OBJECT_TYPE));
}
}
}
/**
* Expect that the first type can be assigned to a symbol of the second
* type.
*
* @param t The node traversal.
* @param n The node to issue warnings
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>.hasCachedValues()) {
subCtor.setPrototypeBasedOn(superObject);
}
}
}
/**
* Expect that the first type can be cast to the second type. The first type
* should be either a subtype or supertype of the second.
*
* @param t The node traversal.
* @param n The node where warnings should point.
* @param type The type being cast from.
* @param castType The type being cast to.
*/
void expectCanCast(NodeTraversal t, Node n, JSType type, JSType castType) {
castType = castType.restrictByNotNullOrUndefined();
type = type.restrictByNotNullOrUndefined();
if (!type.canAssignTo(castType) && !castType.canAssignTo(type)) {
registerMismatch(type, castType, report(t.makeError(n, INVALID_CAST,
castType.toString(), type.toString())));
}
}
/**
* Expect that the given variable has not been declared with a type.
*
* @param sourceName The name of the source file we're in.
* @param n The node where warnings should point to.
* @param parent The parent of {@code n}.
* @param var The variable that we're checking.
* @param variableName The name of the variable.
* @param newType The type being applied to the variable. Mostly just here
* for the benefit of the warning.
* @return The variable we end up with. Most of the time, this will just
* be {@code var}, but in some rare cases we will need to declare
* a new var with new source info.
*/
Var expectUndeclaredVariable(String sourceName, CompilerInput input,
Node n, Node parent, Var var, String variableName, JSType newType) {
Var newVar = var;
boolean allowDupe = false;
if (n.isGetProp() ||
NodeUtil.isObjectLitKey(n, parent)) {
JSDocInfo info = n.getJSDocInfo();
if (info == null) {
info = parent.getJSDocInfo();
}
allowDupe =
info != null && info.getSuppressions().contains("duplicate");
}
JSType varType = var.getType();
// Only report duplicate declarations that have types. Other duplicates
// will be reported by the syntactic scope creator later in the
// compilation process.
if (varType != null &&
varType != typeRegistry.getNativeType(UNKNOWN_TYPE) &&
newType != null &&
newType != typeRegistry.getNativeType(UNKNOWN_TYPE)) {
// If there are two typed declarations of the same variable, that
// is an error and the second declaration is ignored, except in the
// case of native types. A null input type means that the declaration
// was made in TypedScopeCreator#createInitialScope and is a
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>
// native type. We should redeclare it at the new input site.
if (var.input == null) {
Scope s = var.getScope();
s.undeclare(var);
newVar = s.declare(variableName, n, varType, input, false);
n.setJSType(varType);
if (parent.isVar()) {
if (n.getFirstChild() != null) {
n.getFirstChild().setJSType(varType);
}
} else {
Preconditions.checkState(parent.isFunction());
parent.setJSType(varType);
}
} else {
// Always warn about duplicates if the overridden type does not
// match the original type.
//
// If the types match, suppress the warning iff there was a @suppress
// tag, or if the original declaration was a stub.
if (!(allowDupe ||
var.getParentNode().isExprResult()) ||
!newType.equals(varType)) {
report(JSError.make(sourceName, n, DUP_VAR_DECLARATION,
variableName, newType.toString(), var.getInputName(),
String.valueOf(var.nameNode.getLineno()),
varType.toString()));
}
}
}
return newVar;
}
/**
* Expect that all properties on interfaces that this type implements are
* implemented and correctly typed.
*/
void expectAllInterfaceProperties(NodeTraversal t, Node n,
FunctionType type) {
ObjectType instance = type.getInstanceType();
for (ObjectType implemented : type.getAllImplementedInterfaces()) {
if (implemented.getImplicitPrototype() != null) {
for (String prop :
implemented.getImplicitPrototype().getOwnPropertyNames()) {
expectInterfaceProperty(t, n, instance, implemented, prop);
}
}
}
}
/**
* Expect that the property in an interface that this type implements is
* implemented and correctly typed.
*/
private void expectInterfaceProperty(NodeTraversal t, Node n,
ObjectType instance, ObjectType implementedInterface, String prop) {
StaticSlot<JSType> propSlot = instance.getSlot(prop);
if (propSlot == null) {
// Not implemented
String sourceName = n.getSourceFileName();
sourceName = sourceName == null ? "" : sourceName;
registerMismatch(instance, implementedInterface,
report(JSError.make(sourceName, n,
INTERFACE_METHOD_NOT_IMPLEMENTED,
prop, implementedInterface.toString(), instance.toString())));
} else {
Node propNode = propSlot.getDeclaration() == null ?
null : propSlot.getDeclaration().getNode();
// Fall back on the constructor node if we can't find a node for the
// property.
propNode = propNode == null ? n : propNode;
JSType found = propSlot.getType();
JSType required
= implementedInterface.getImplicitPrototype().getPropertyType(prop);
found = found.
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> }
private JSType getNativeType(JSTypeNative typeId) {
return typeRegistry.getNativeType(typeId);
}
private JSError report(JSError error) {
if (shouldReport) {
compiler.report(error);
}
return error;
}
/**
* Signals that the first type and the second type have been
* used interchangeably.
*
* Type-based optimizations should take this into account
* so that they don't wreck code with type warnings.
*/
static class TypeMismatch {
final JSType typeA;
final JSType typeB;
final JSError src;
/**
* It's the responsibility of the class that creates the
* {@code TypeMismatch} to ensure that {@code a} and {@code b} are
* non-matching types.
*/
TypeMismatch(JSType a, JSType b, JSError src) {
this.typeA = a;
this.typeB = b;
this.src = src;
}
@Override public boolean equals(Object object) {
if (object instanceof TypeMismatch) {
TypeMismatch that = (TypeMismatch) object;
return (that.typeA.equals(this.typeA) && that.typeB.equals(this.typeB))
|| (that.typeB.equals(this.typeA) && that.typeA.equals(this.typeB));
}
return false;
}
@Override public int hashCode() {
return Objects.hashCode(typeA, typeB);
}
@Override public String toString() {
return "(" + typeA + ", " + typeB + ")";
}
}
}
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> * Merging function for strict equality between types.
*/
private static final
Function<TypePair, TypePair> SHEQ =
new Function<TypePair, TypePair>() {
@Override
public TypePair apply(TypePair p) {
if (p.typeA == null || p.typeB == null) {
return null;
}
return p.typeA.getTypesUnderShallowEquality(p.typeB);
}
};
/**
* Merging function for strict non-equality between types.
*/
private static final
Function<TypePair, TypePair> SHNE =
new Function<TypePair, TypePair>() {
@Override
public TypePair apply(TypePair p) {
if (p.typeA == null || p.typeB == null) {
return null;
}
return p.typeA.getTypesUnderShallowInequality(p.typeB);
}
};
/**
* Merging function for inequality comparisons between types.
*/
private final
Function<TypePair, TypePair> INEQ =
new Function<TypePair, TypePair>() {
@Override
public TypePair apply(TypePair p) {
return new TypePair(
getRestrictedWithoutUndefined(p.typeA),
getRestrictedWithoutUndefined(p.typeB));
}
};
/**
* Creates a semantic reverse abstract interpreter.
*/
public SemanticReverseAbstractInterpreter(CodingConvention convention,
JSTypeRegistry typeRegistry) {
super(convention, typeRegistry);
}
@Override
public FlowScope getPreciserScopeKnowingConditionOutcome(Node condition,
FlowScope blindScope, boolean outcome) {
// Check for the typeof operator.
int operatorToken = condition.getType();
switch (operatorToken) {
case Token.EQ:
case Token.NE:
case Token.SHEQ:
case Token.SHNE:
case Token.CASE:
Node left;
Node right;
if (operatorToken == Token.CASE) {
left = condition.getParent().getFirstChild(); // the switch condition
right = condition.getFirstChild();
} else {
left = condition.getFirstChild();
right = condition.getLastChild();
}
Node typeOfNode = null;
Node stringNode = null;
if (left.isTypeOf() && right.isString()) {
typeOfNode = left;
stringNode = right;
} else if (right.isTypeOf() &&
left.isString()) {
typeOfNode = right;
stringNode = left;
}
if (typeOfNode != null && stringNode != null) {
Node operandNode = typeOfNode.getFirstChild();
JSType operandType = getTypeIfRefinable(operandNode, blindScope);
if (operandType != null) {
boolean resultEqualsValue = operatorToken == Token.EQ ||
operatorToken == Token.SHEQ || operatorToken == Token.CASE;
if (!outcome) {
resultEqualsValue = !result
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>IsRefineable = true;
} else {
rightIsRefineable = false;
rightType = right.getJSType();
blindScope = firstPreciserScopeKnowingConditionOutcome(
right, blindScope, condition);
}
if (condition) {
rightType = (rightType == null) ? null :
rightType.getRestrictedTypeGivenToBooleanOutcome(condition);
// creating new scope
if ((leftType != null && leftIsRefineable) ||
(rightType != null && rightIsRefineable)) {
FlowScope informed = blindScope.createChildFlowScope();
if (leftIsRefineable && leftType != null) {
declareNameInScope(informed, left, leftType);
}
if (rightIsRefineable && rightType != null) {
declareNameInScope(informed, right, rightType);
}
return informed;
}
}
return blindScope;
}
private FlowScope caseAndOrMaybeShortCircuiting(Node left, Node right,
FlowScope blindScope, boolean condition) {
FlowScope leftScope = firstPreciserScopeKnowingConditionOutcome(
left, blindScope, !condition);
StaticSlot<JSType> leftVar = leftScope.findUniqueRefinedSlot(blindScope);
if (leftVar == null) {
return blindScope;
}
FlowScope rightScope = firstPreciserScopeKnowingConditionOutcome(
left, blindScope, condition);
rightScope = firstPreciserScopeKnowingConditionOutcome(
right, rightScope, !condition);
StaticSlot<JSType> rightVar = rightScope.findUniqueRefinedSlot(blindScope);
if (rightVar == null || !leftVar.getName().equals(rightVar.getName())) {
return blindScope;
}
JSType type = leftVar.getType().getLeastSupertype(rightVar.getType());
FlowScope informed = blindScope.createChildFlowScope();
informed.inferSlotType(leftVar.getName(), type);
return informed;
}
private FlowScope caseNameOrGetProp(Node name, FlowScope blindScope,
boolean outcome) {
JSType type = getTypeIfRefinable(name, blindScope);
if (type != null) {
JSType restrictedType =
type.getRestrictedTypeGivenToBooleanOutcome(outcome);
FlowScope informed = blindScope.createChildFlowScope();
declareNameInScope(informed, name, restrictedType);
return informed;
}
return blindScope;
}
private FlowScope caseTypeOf(Node node, JSType type, String value,
boolean resultEqualsValue, FlowScope blindScope) {
JSType restrictedType =
getRestrictedByTypeOfResult(type, value, resultEqualsValue);
if (restrictedType == null) {
return blindScope;
}
FlowScope informed = blindScope.createChildFlowScope();
declareNameIn
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>Scope(informed, node, restrictedType);
return informed;
}
private FlowScope caseInstanceOf(Node left, Node right, FlowScope blindScope,
boolean outcome) {
JSType leftType = getTypeIfRefinable(left, blindScope);
if (leftType == null) {
return blindScope;
}
JSType rightType = right.getJSType();
ObjectType targetType =
typeRegistry.getNativeObjectType(JSTypeNative.UNKNOWN_TYPE);
if (rightType != null && rightType.isFunctionType()) {
targetType = rightType.toMaybeFunctionType();
}
Visitor<JSType> visitor;
if (outcome) {
visitor = new RestrictByTrueInstanceOfResultVisitor(targetType);
} else {
visitor = new RestrictByFalseInstanceOfResultVisitor(targetType);
}
JSType restrictedLeftType = leftType.visit(visitor);
if (restrictedLeftType != null && !restrictedLeftType.equals(leftType)) {
FlowScope informed = blindScope.createChildFlowScope();
declareNameInScope(informed, left, restrictedLeftType);
return informed;
}
return blindScope;
}
/**
* Given 'property in object', ensures that the object has the property in the
* informed scope by defining it as a qualified name if the object type lacks
* the property and it's not in the blind scope.
* @param object The node of the right-side of the in.
* @param propertyName The string of the left-side of the in.
*/
private FlowScope caseIn(Node object, String propertyName, FlowScope blindScope) {
JSType jsType = object.getJSType();
jsType = this.getRestrictedWithoutNull(jsType);
jsType = this.getRestrictedWithoutUndefined(jsType);
boolean hasProperty = false;
ObjectType objectType = ObjectType.cast(jsType);
if (objectType != null) {
hasProperty = objectType.hasProperty(propertyName);
}
if (!hasProperty) {
String qualifiedName = object.getQualifiedName();
if (qualifiedName != null) {
String propertyQualifiedName = qualifiedName + "." + propertyName;
if (blindScope.getSlot(propertyQualifiedName) == null) {
FlowScope informed = blindScope.createChildFlowScope();
JSType unknownType = typeRegistry.getNativeType(
JSTypeNative.UNKNOWN_TYPE);
informed.inferQualifiedSlot(
object, propertyQualifiedName, unknownType, unknownType);
return informed;
}
}
}
return blindScope;
}
/**
* @see SemanticReverseAbstractInterpreter#caseInstanceOf
*/
private class RestrictByTrueInstanceOfResultVisitor
extends RestrictByTrueTypeOfResultVisitor {
private final ObjectType target;
RestrictByTrueInstanceOfResultVisitor(ObjectType target) {
this.target = target;
}
@Override
protected JSType caseTopType(JSType type
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>9 || c == 0xC || c == 0xB;
} else {
return c == 0xA0
|| Character.getType((char)c) == Character.SPACE_SEPARATOR;
}
}
private static boolean isJSFormatChar(int c) {
return c > 127 && Character.getType((char)c) == Character.FORMAT;
}
/**
* Allows the JSDocParser to update the character offset
* so that getCharno() returns a valid character position.
*/
void update() {
charno = getOffset();
}
private int peekChar() {
int c = getChar();
ungetChar(c);
return c;
}
protected int getChar() {
if (ungetCursor != 0) {
cursor++;
--ungetCursor;
if (charno == -1) {
charno = getOffset();
}
return ungetBuffer[ungetCursor];
}
for(;;) {
int c;
if (sourceCursor == sourceEnd) {
hitEOF = true;
if (charno == -1) {
charno = getOffset();
}
return EOF_CHAR;
}
cursor++;
c = sourceString.charAt(sourceCursor++);
if (lineEndChar >= 0) {
if (lineEndChar == '\r' && c == '\n') {
lineEndChar = '\n';
continue;
}
lineEndChar = -1;
lineStart = sourceCursor - 1;
lineno++;
}
if (c <= 127) {
if (c == '\n' || c == '\r') {
lineEndChar = c;
c = '\n';
}
} else {
if (isJSFormatChar(c)) {
continue;
}
if (ScriptRuntime.isJSLineTerminator(c)) {
lineEndChar = c;
c = '\n';
}
}
if (charno == -1) {
charno = getOffset();
}
return c;
}
}
private int getCharIgnoreLineEnd() {
if (ungetCursor != 0) {
cursor++;
--ungetCursor;
if (charno == -1) {
charno = getOffset();
}
return ungetBuffer[ungetCursor];
}
for(;;) {
int c;
if (sourceCursor == sourceEnd) {
hitEOF = true;
if (charno == -1) {
charno = getOffset();
}
return EOF_CHAR;
}
cursor++;
c = sourceString.charAt(sourceCursor++);
if (c <= 127) {
if (c == '\n' || c == '\r') {
lineEndChar = c;
c = '\n';
}
} else {
if (isJSFormatChar(c)) {
continue;
}
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> there is no reason to traverse non global contexts.
*/
@Override
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
if (n.isFunction()) {
// Don't traverse functions that are constructors or have the @this
// or @override annotation.
JSDocInfo jsDoc = getFunctionJsDocInfo(n);
if (jsDoc != null &&
(jsDoc.isConstructor() ||
jsDoc.isInterface() ||
jsDoc.hasThisType() ||
jsDoc.isOverride())) {
return false;
}
// Don't traverse functions unless they would normally
// be able to have a @this annotation associated with them. e.g.,
// var a = function() { }; // or
// function a() {} // or
// a.x = function() {}; // or
// var a = {x: function() {}};
int pType = parent.getType();
if (!(pType == Token.BLOCK ||
pType == Token.SCRIPT ||
pType == Token.NAME ||
pType == Token.ASSIGN ||
// object literal keys
pType == Token.STRING_KEY)) {
return false;
}
// Don't traverse functions that are getting lent to a prototype.
Node gramps = parent.getParent();
if (NodeUtil.isObjectLitKey(parent, gramps)) {
JSDocInfo maybeLends = gramps.getJSDocInfo();
if (maybeLends != null &&
maybeLends.getLendsName() != null &&
maybeLends.getLendsName().endsWith(".prototype")) {
return false;
}
}
}
if (parent != null && parent.isAssign()) {
Node lhs = parent.getFirstChild();
Node rhs = lhs.getNext();
if (n == lhs) {
// Always traverse the left side of the assignment. To handle
// nested assignments properly (e.g., (a = this).property = c;),
// assignLhsChild should not be overridden.
if (assignLhsChild == null) {
assignLhsChild = lhs;
}
} else {
// Only traverse the right side if it's not an assignment to a prototype
// property or subproperty.
if (NodeUtil.isGet(lhs)) {
if (lhs.isGetProp() &&
lhs.getLastChild().getString().equals("prototype")) {
return false;
}
Node llhs = lhs.getFirstChild();
if (llhs.isGetProp() &&
llhs.getLastChild().getString().equals("prototype")) {
return false;
}
}
}
}
return true;
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.isThis() && shouldReportThis(n, parent)) {
compiler.report(t.makeError(n, GLOBAL_THIS));
}
if (n
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> == assignLhsChild) {
assignLhsChild = null;
}
}
private boolean shouldReportThis(Node n, Node parent) {
if (assignLhsChild != null) {
// Always report a THIS on the left side of an assign.
return true;
}
// Also report a THIS with a property access.
return parent != null && NodeUtil.isGet(parent);
}
/**
* Gets a function's JSDoc information, if it has any. Checks for a few
* patterns (ellipses show where JSDoc would be):
* <pre>
* ... function() {}
* ... x = function() {};
* var ... x = function() {};
* ... var x = function() {};
* </pre>
*/
private JSDocInfo getFunctionJsDocInfo(Node n) {
JSDocInfo jsDoc = n.getJSDocInfo();
Node parent = n.getParent();
if (jsDoc == null) {
int parentType = parent.getType();
if (parentType == Token.NAME || parentType == Token.ASSIGN) {
jsDoc = parent.getJSDocInfo();
if (jsDoc == null && parentType == Token.NAME) {
Node gramps = parent.getParent();
if (gramps.isVar()) {
jsDoc = gramps.getJSDocInfo();
}
}
}
}
return jsDoc;
}
}
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> */
public RecordTypeBuilder addProperty(String name, JSType type, Node
propertyNode) {
isEmpty = false;
if (properties.containsKey(name)) {
return null;
}
properties.put(name, new RecordProperty(type, propertyNode));
return this;
}
/**
* Creates a record.
* @return The record type.
*/
public JSType build() {
// If we have an empty record, simply return the object type.
if (isEmpty) {
return registry.getNativeObjectType(JSTypeNative.OBJECT_TYPE);
}
return new RecordType(
registry, Collections.unmodifiableMap(properties), isDeclared);
}
static class RecordProperty {
private final JSType type;
private final Node propertyNode;
RecordProperty(JSType type, Node propertyNode) {
this.type = type;
this.propertyNode = propertyNode;
}
public JSType getType() {
return type;
}
public Node getPropertyNode() {
return propertyNode;
}
}
}
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>s literal types");
static final DiagnosticType INVALID_DEFINE_INIT_ERROR =
DiagnosticType.error(
"JSC_INVALID_DEFINE_INIT_ERROR",
"illegal initialization of @define variable {0}");
static final DiagnosticType NON_GLOBAL_DEFINE_INIT_ERROR =
DiagnosticType.error(
"JSC_NON_GLOBAL_DEFINE_INIT_ERROR",
"@define variable {0} assignment must be global");
static final DiagnosticType DEFINE_NOT_ASSIGNABLE_ERROR =
DiagnosticType.error(
"JSC_DEFINE_NOT_ASSIGNABLE_ERROR",
"@define variable {0} cannot be reassigned due to code at {1}.");
private static final MessageFormat REASON_DEFINE_NOT_ASSIGNABLE =
new MessageFormat("line {0} of {1}");
/**
* Create a pass that overrides define constants.
*
* TODO(nicksantos): Write a builder to help JSCompiler induce
* {@code replacements} from command-line flags
*
* @param replacements A hash table of names of defines to their replacements.
* All replacements <b>must</b> be literals.
*/
ProcessDefines(AbstractCompiler compiler, Map<String, Node> replacements) {
this.compiler = compiler;
dominantReplacements = replacements;
}
/**
* Injects a pre-computed global namespace, so that the same namespace
* can be re-used for multiple check passes. Returns {@code this} for
* easy chaining.
*/
ProcessDefines injectNamespace(GlobalNamespace namespace) {
this.namespace = namespace;
return this;
}
@Override
public void process(Node externs, Node root) {
if (namespace == null) {
namespace = new GlobalNamespace(compiler, root);
}
overrideDefines(collectDefines(root, namespace));
}
private void overrideDefines(Map<String, DefineInfo> allDefines) {
boolean changed = false;
for (Map.Entry<String, DefineInfo> def : allDefines.entrySet()) {
String defineName = def.getKey();
DefineInfo info = def.getValue();
Node inputValue = dominantReplacements.get(defineName);
Node finalValue = inputValue != null ?
inputValue : info.getLastValue();
if (finalValue != info.initialValue) {
info.initialValueParent.replaceChild(
info.initialValue, finalValue.cloneTree());
compiler.addToDebugLog("Overriding @define variable " + defineName);
changed = changed ||
finalValue.getType() != info.initialValue.getType() ||
!finalValue.isEquivalentTo(info.initialValue);
}
}
if (changed) {
compiler.reportCodeChange();
}
Set<String> unusedReplacements = dominantReplacements.keySet();
unusedReplacements.removeAll(allDefines.keySet());
unusedReplacements.removeAll(KNOWN_DEFINES);
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> for (String unknownDefine : unusedReplacements) {
compiler.report(JSError.make(UNKNOWN_DEFINE_WARNING, unknownDefine));
}
}
private static String format(MessageFormat format, Object... params) {
return format.format(params);
}
/**
* Only defines of literal number, string, or boolean are supported.
*/
private boolean isValidDefineType(JSTypeExpression expression) {
JSType type = expression.evaluate(null, compiler.getTypeRegistry());
return !type.isUnknownType() && type.isSubtype(
compiler.getTypeRegistry().getNativeType(
JSTypeNative.NUMBER_STRING_BOOLEAN));
}
/**
* Finds all defines, and creates a {@link DefineInfo} data structure for
* each one.
* @return A map of {@link DefineInfo} structures, keyed by name.
*/
private Map<String, DefineInfo> collectDefines(Node root,
GlobalNamespace namespace) {
// Find all the global names with a @define annotation
List<Name> allDefines = Lists.newArrayList();
for (Name name : namespace.getNameIndex().values()) {
Ref decl = name.getDeclaration();
if (name.docInfo != null && name.docInfo.isDefine()) {
// Process defines should not depend on check types being enabled,
// so we look for the JSDoc instead of the inferred type.
if (isValidDefineType(name.docInfo.getType())) {
allDefines.add(name);
} else {
JSError error = JSError.make(
decl.getSourceName(),
decl.node, INVALID_DEFINE_TYPE_ERROR);
compiler.report(error);
}
} else {
for (Ref ref : name.getRefs()) {
if (ref == decl) {
// Declarations were handled above.
continue;
}
Node n = ref.node;
Node parent = ref.node.getParent();
JSDocInfo info = n.getJSDocInfo();
if (info == null &&
parent.isVar() && parent.hasOneChild()) {
info = parent.getJSDocInfo();
}
if (info != null && info.isDefine()) {
allDefines.add(name);
break;
}
}
}
}
CollectDefines pass = new CollectDefines(compiler, allDefines);
NodeTraversal.traverse(compiler, root, pass);
return pass.getAllDefines();
}
/**
* Finds all assignments to @defines, and figures out the last value of
* the @define.
*/
private static final class CollectDefines implements Callback {
private final AbstractCompiler compiler;
private final Map<String, DefineInfo> assignableDefines;
private final Map<String, DefineInfo> allDefines;
private final Map<Node, RefInfo> allRefInfo;
// A hack that allows us to remove ASSIGN/VAR statements when
// we're currently visiting one of the children of the assign
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> // DEF_FOO = 5; // becomes "5;"
// We can't remove the ASSIGN/VAR when we're still visiting its
// children, so we'll have to come back later to remove it.
refInfo.name.removeRef(ref);
lvalueToRemoveLater = valParent;
}
break;
default:
if (t.inGlobalScope()) {
// Treat this as a reference to a define in the global scope.
// After this point, the define must not be reassigned,
// or it's an error.
DefineInfo info = assignableDefines.get(fullName);
if (info != null) {
setDefineInfoNotAssignable(info, t);
assignableDefines.remove(fullName);
}
}
break;
}
}
if (!t.inGlobalScope() &&
n.getJSDocInfo() != null && n.getJSDocInfo().isDefine()) {
// warn about @define annotations in local scopes
compiler.report(
t.makeError(n, NON_GLOBAL_DEFINE_INIT_ERROR, ""));
}
if (lvalueToRemoveLater == n) {
lvalueToRemoveLater = null;
if (n.isAssign()) {
Node last = n.getLastChild();
n.removeChild(last);
parent.replaceChild(n, last);
} else {
Preconditions.checkState(n.isName());
n.removeChild(n.getFirstChild());
}
compiler.reportCodeChange();
}
if (n.isCall()) {
if (t.inGlobalScope()) {
// If there's a function call in the global scope,
// we just say it's unsafe and freeze all the defines.
//
// NOTE(nicksantos): We could be a lot smarter here. For example,
// ReplaceOverriddenVars keeps a call graph of all functions and
// which functions/variables that they reference, and tries
// to statically determine which functions are "safe" and which
// are not. But this would be overkill, especially because
// the intended use of defines is with config_files, where
// all the defines are at the top of the bundle.
for (DefineInfo info : assignableDefines.values()) {
setDefineInfoNotAssignable(info, t);
}
assignableDefines.clear();
}
}
updateAssignAllowedStack(n, false);
}
/**
* Determines whether assignment to a define should be allowed
* in the subtree of the given node, and if not, records that fact.
*
* @param n The node whose subtree we're about to enter or exit.
* @param entering True if we're entering the subtree, false otherwise.
*/
private void updateAssignAllowedStack(Node n, boolean entering) {
switch (n.getType()) {
case Token.CASE:
case Token.FOR:
case Token.FUNCTION:
case Token.HOOK:
case Token.IF
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>.isOr()) {
// When handling the short-circuiting binary operators,
// the outcome scope on true can be different than the outcome
// scope on false.
//
// TODO(nicksantos): The "right" way to do this is to
// carry the known outcome all the way through the
// recursive traversal, so that we can construct a
// different flow scope based on the outcome. However,
// this would require a bunch of code and a bunch of
// extra computation for an edge case. This seems to be
// a "good enough" approximation.
// conditionOutcomes is cached from previous iterations
// of the loop.
if (conditionOutcomes == null) {
conditionOutcomes = condition.isAnd() ?
traverseAnd(condition, output.createChildFlowScope()) :
traverseOr(condition, output.createChildFlowScope());
}
newScope =
reverseInterpreter.getPreciserScopeKnowingConditionOutcome(
condition,
conditionOutcomes.getOutcomeFlowScope(
condition.getType(), branch == Branch.ON_TRUE),
branch == Branch.ON_TRUE);
} else {
// conditionFlowScope is cached from previous iterations
// of the loop.
if (conditionFlowScope == null) {
conditionFlowScope =
traverse(condition, output.createChildFlowScope());
}
newScope =
reverseInterpreter.getPreciserScopeKnowingConditionOutcome(
condition, conditionFlowScope, branch == Branch.ON_TRUE);
}
}
break;
}
result.add(newScope.optimize());
}
return result;
}
private FlowScope traverse(Node n, FlowScope scope) {
switch (n.getType()) {
case Token.ASSIGN:
scope = traverseAssign(n, scope);
break;
case Token.NAME:
scope = traverseName(n, scope);
break;
case Token.GETPROP:
scope = traverseGetProp(n, scope);
break;
case Token.AND:
scope = traverseAnd(n, scope).getJoinedFlowScope()
.createChildFlowScope();
break;
case Token.OR:
scope = traverseOr(n, scope).getJoinedFlowScope()
.createChildFlowScope();
break;
case Token.HOOK:
scope = traverseHook(n, scope);
break;
case Token.OBJECTLIT:
scope = traverseObjectLiteral(n, scope);
break;
case Token.CALL:
scope = traverseCall(n, scope);
break;
case Token.NEW:
scope = traverseNew(n, scope);
break;
case Token.ASSIGN_ADD:
case Token.ADD:
scope = traverseAdd(n, scope);
break;
case Token.POS:
case Token.NEG:
scope = traverse(n.getFirstChild(), scope); // Find types.
n.setJSType(getNativeType(NUMBER_TYPE));
break;
case Token
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>.ARRAYLIT:
scope = traverseArrayLiteral(n, scope);
break;
case Token.THIS:
n.setJSType(scope.getTypeOfThis());
break;
case Token.ASSIGN_LSH:
case Token.ASSIGN_RSH:
case Token.LSH:
case Token.RSH:
case Token.ASSIGN_URSH:
case Token.URSH:
case Token.ASSIGN_DIV:
case Token.ASSIGN_MOD:
case Token.ASSIGN_BITAND:
case Token.ASSIGN_BITXOR:
case Token.ASSIGN_BITOR:
case Token.ASSIGN_MUL:
case Token.ASSIGN_SUB:
case Token.DIV:
case Token.MOD:
case Token.BITAND:
case Token.BITXOR:
case Token.BITOR:
case Token.MUL:
case Token.SUB:
case Token.DEC:
case Token.INC:
case Token.BITNOT:
scope = traverseChildren(n, scope);
n.setJSType(getNativeType(NUMBER_TYPE));
break;
case Token.PARAM_LIST:
scope = traverse(n.getFirstChild(), scope);
n.setJSType(getJSType(n.getFirstChild()));
break;
case Token.COMMA:
scope = traverseChildren(n, scope);
n.setJSType(getJSType(n.getLastChild()));
break;
case Token.TYPEOF:
scope = traverseChildren(n, scope);
n.setJSType(getNativeType(STRING_TYPE));
break;
case Token.DELPROP:
case Token.LT:
case Token.LE:
case Token.GT:
case Token.GE:
case Token.NOT:
case Token.EQ:
case Token.NE:
case Token.SHEQ:
case Token.SHNE:
case Token.INSTANCEOF:
case Token.IN:
scope = traverseChildren(n, scope);
n.setJSType(getNativeType(BOOLEAN_TYPE));
break;
case Token.GETELEM:
scope = traverseGetElem(n, scope);
break;
case Token.EXPR_RESULT:
scope = traverseChildren(n, scope);
if (n.getFirstChild().isGetProp()) {
ensurePropertyDeclared(n.getFirstChild());
}
break;
case Token.SWITCH:
scope = traverse(n.getFirstChild(), scope);
break;
case Token.RETURN:
scope = traverseReturn(n, scope);
break;
case Token.VAR:
case Token.THROW:
scope = traverseChildren(n, scope);
break;
case Token.CATCH:
scope = traverseCatch(n, scope);
break;
}
if (!n.isFunction()) {
JSDocInfo info = n.getJSDocInfo();
if (info != null && info.hasType()) {
JSType castType = info.getType().evaluate
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>(syntacticScope, registry);
// A stubbed type cast on a qualified name should take
// effect for all subsequent accesses of that name,
// so treat it the same as an assign to that name.
if (n.isQualifiedName() &&
n.getParent().isExprResult()) {
updateScopeForTypeChange(scope, n, n.getJSType(), castType);
}
n.setJSType(castType);
}
}
return scope;
}
/**
* Traverse a return value.
*/
private FlowScope traverseReturn(Node n, FlowScope scope) {
scope = traverseChildren(n, scope);
Node retValue = n.getFirstChild();
if (retValue != null) {
JSType type = functionScope.getRootNode().getJSType();
if (type != null) {
FunctionType fnType = type.toMaybeFunctionType();
if (fnType != null) {
inferPropertyTypesToMatchConstraint(
retValue.getJSType(), fnType.getReturnType());
}
}
}
return scope;
}
/**
* Any value can be thrown, so it's really impossible to determine the type
* of a CATCH param. Treat it as the UNKNOWN type.
*/
private FlowScope traverseCatch(Node n, FlowScope scope) {
Node name = n.getFirstChild();
JSType type = getNativeType(JSTypeNative.UNKNOWN_TYPE);
name.setJSType(type);
redeclareSimpleVar(scope, name, type);
return scope;
}
private FlowScope traverseAssign(Node n, FlowScope scope) {
Node left = n.getFirstChild();
Node right = n.getLastChild();
scope = traverseChildren(n, scope);
JSType leftType = left.getJSType();
JSType rightType = getJSType(right);
n.setJSType(rightType);
updateScopeForTypeChange(scope, left, leftType, rightType);
return scope;
}
/**
* Updates the scope according to the result of a type change, like
* an assignment or a type cast.
*/
private void updateScopeForTypeChange(
FlowScope scope, Node left, JSType leftType, JSType resultType) {
Preconditions.checkNotNull(resultType);
switch (left.getType()) {
case Token.NAME:
String varName = left.getString();
Var var = syntacticScope.getVar(varName);
// When looking at VAR initializers for declared VARs, we trust
// the declared type over the type it's being initialized to.
// This has two purposes:
// 1) We avoid re-declaring declared variables so that built-in
// types defined in externs are not redeclared.
// 2) When there's a lexical closure like
// /** @type {?string} */ var x = null;
// function f() { x = 'xyz'; }
// the
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> inference will ignore the lexical closure,
// which is just wrong. This bug needs to be fixed eventually.
boolean isVarDeclaration = left.hasChildren();
if (!isVarDeclaration || var == null || var.isTypeInferred()) {
redeclareSimpleVar(scope, left, resultType);
}
left.setJSType(isVarDeclaration || leftType == null ?
resultType : null);
if (var != null && var.isTypeInferred()) {
JSType oldType = var.getType();
var.setType(oldType == null ?
resultType : oldType.getLeastSupertype(resultType));
}
break;
case Token.GETPROP:
String qualifiedName = left.getQualifiedName();
if (qualifiedName != null) {
scope.inferQualifiedSlot(left, qualifiedName,
leftType == null ? getNativeType(UNKNOWN_TYPE) : leftType,
resultType);
}
left.setJSType(resultType);
ensurePropertyDefined(left, resultType);
break;
}
}
/**
* Defines a property if the property has not been defined yet.
*/
private void ensurePropertyDefined(Node getprop, JSType rightType) {
String propName = getprop.getLastChild().getString();
JSType nodeType = getJSType(getprop.getFirstChild());
ObjectType objectType = ObjectType.cast(
nodeType.restrictByNotNullOrUndefined());
if (objectType == null) {
registry.registerPropertyOnType(propName, nodeType);
} else {
if (ensurePropertyDeclaredHelper(getprop, objectType)) {
return;
}
if (!objectType.isPropertyTypeDeclared(propName)) {
// We do not want a "stray" assign to define an inferred property
// for every object of this type in the program. So we use a heuristic
// approach to determine whether to infer the property.
//
// 1) If the property is already defined, join it with the previously
// inferred type.
// 2) If this isn't an instance object, define it.
// 3) If the property of an object is being assigned in the constructor,
// define it.
// 4) If this is a stub, define it.
// 5) Otherwise, do not define the type, but declare it in the registry
// so that we can use it for missing property checks.
if (objectType.hasProperty(propName) ||
!objectType.isInstanceType()) {
if ("prototype".equals(propName)) {
objectType.defineDeclaredProperty(
propName, rightType, getprop);
} else {
objectType.defineInferredProperty(
propName, rightType, getprop);
}
} else {
if (getprop.getFirstChild().isThis() &&
getJSType(syntacticScope.getRootNode()).isConstructor()) {
objectType.define
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>InferredProperty(
propName, rightType, getprop);
} else {
registry.registerPropertyOnType(propName, objectType);
}
}
}
}
}
/**
* Defines a declared property if it has not been defined yet.
*
* This handles the case where a property is declared on an object where
* the object type is inferred, and so the object type will not
* be known in {@code TypedScopeCreator}.
*/
private void ensurePropertyDeclared(Node getprop) {
ObjectType ownerType = ObjectType.cast(
getJSType(getprop.getFirstChild()).restrictByNotNullOrUndefined());
if (ownerType != null) {
ensurePropertyDeclaredHelper(getprop, ownerType);
}
}
/**
* Declares a property on its owner, if necessary.
* @return True if a property was declared.
*/
private boolean ensurePropertyDeclaredHelper(
Node getprop, ObjectType objectType) {
String propName = getprop.getLastChild().getString();
String qName = getprop.getQualifiedName();
if (qName != null) {
Var var = syntacticScope.getVar(qName);
if (var != null && !var.isTypeInferred()) {
// Handle normal declarations that could not be addressed earlier.
if (propName.equals("prototype") ||
// Handle prototype declarations that could not be addressed earlier.
(!objectType.hasOwnProperty(propName) &&
(!objectType.isInstanceType() ||
(var.isExtern() && !objectType.isNativeObjectType())))) {
return objectType.defineDeclaredProperty(
propName, var.getType(), getprop);
}
}
}
return false;
}
private FlowScope traverseName(Node n, FlowScope scope) {
String varName = n.getString();
Node value = n.getFirstChild();
JSType type = n.getJSType();
if (value != null) {
scope = traverse(value, scope);
updateScopeForTypeChange(scope, n, n.getJSType() /* could be null */,
getJSType(value));
return scope;
} else {
StaticSlot<JSType> var = scope.getSlot(varName);
if (var != null) {
// There are two situations where we don't want to use type information
// from the scope, even if we have it.
// 1) The var is escaped in a weird way, e.g.,
// function f() { var x = 3; function g() { x = null } (x); }
boolean isInferred = var.isTypeInferred();
boolean unflowable = isInferred &&
isUnflowable(syntacticScope.getVar(varName));
// 2) We're reading type information from another scope for an
// inferred variable.
// var t = null; function f() { (t); }
boolean nonLocalInferredSlot =
isIn
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>ferred &&
syntacticScope.getParent() != null &&
var == syntacticScope.getParent().getSlot(varName);
if (!unflowable && !nonLocalInferredSlot) {
type = var.getType();
if (type == null) {
type = getNativeType(UNKNOWN_TYPE);
}
}
}
}
n.setJSType(type);
return scope;
}
/** Traverse each element of the array. */
private FlowScope traverseArrayLiteral(Node n, FlowScope scope) {
scope = traverseChildren(n, scope);
n.setJSType(getNativeType(ARRAY_TYPE));
return scope;
}
private FlowScope traverseObjectLiteral(Node n, FlowScope scope) {
JSType type = n.getJSType();
Preconditions.checkNotNull(type);
for (Node name = n.getFirstChild(); name != null; name = name.getNext()) {
scope = traverse(name.getFirstChild(), scope);
}
// Object literals can be reflected on other types, or changed with
// type casts.
// See CodingConvention#getObjectLiteralCase and goog.object.reflect.
// Ignore these types of literals.
// TODO(nicksantos): There should be an "anonymous object" type that
// we can check for here.
ObjectType objectType = ObjectType.cast(type);
if (objectType == null) {
return scope;
}
boolean hasLendsName = n.getJSDocInfo() != null &&
n.getJSDocInfo().getLendsName() != null;
if (objectType.hasReferenceName() && !hasLendsName) {
return scope;
}
String qObjName = NodeUtil.getBestLValueName(
NodeUtil.getBestLValue(n));
for (Node name = n.getFirstChild(); name != null;
name = name.getNext()) {
Node value = name.getFirstChild();
String memberName = NodeUtil.getObjectLitKeyName(name);
if (memberName != null) {
JSType rawValueType = name.getFirstChild().getJSType();
JSType valueType = NodeUtil.getObjectLitKeyTypeFromValueType(
name, rawValueType);
if (valueType == null) {
valueType = getNativeType(UNKNOWN_TYPE);
}
objectType.defineInferredProperty(memberName, valueType, name);
// Do normal flow inference if this is a direct property assignment.
if (qObjName != null && name.isStringKey()) {
String qKeyName = qObjName + "." + memberName;
Var var = syntacticScope.getVar(qKeyName);
JSType oldType = var == null ? null : var.getType();
if (var != null && var.isTypeInferred()) {
var.setType(oldType == null ?
valueType : oldType.getLeastSupertype(oldType));
}
scope.inferQualifiedSlot(
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>name, qKeyName,
oldType == null ? getNativeType(UNKNOWN_TYPE) : oldType,
valueType);
}
} else {
n.setJSType(getNativeType(UNKNOWN_TYPE));
}
}
return scope;
}
private FlowScope traverseAdd(Node n, FlowScope scope) {
Node left = n.getFirstChild();
Node right = left.getNext();
scope = traverseChildren(n, scope);
JSType leftType = left.getJSType();
JSType rightType = right.getJSType();
JSType type = getNativeType(UNKNOWN_TYPE);
if (leftType != null && rightType != null) {
boolean leftIsUnknown = leftType.isUnknownType();
boolean rightIsUnknown = rightType.isUnknownType();
if (leftIsUnknown && rightIsUnknown) {
type = getNativeType(UNKNOWN_TYPE);
} else if ((!leftIsUnknown && leftType.isString()) ||
(!rightIsUnknown && rightType.isString())) {
type = getNativeType(STRING_TYPE);
} else if (leftIsUnknown || rightIsUnknown) {
type = getNativeType(UNKNOWN_TYPE);
} else if (isAddedAsNumber(leftType) && isAddedAsNumber(rightType)) {
type = getNativeType(NUMBER_TYPE);
} else {
type = registry.createUnionType(STRING_TYPE, NUMBER_TYPE);
}
}
n.setJSType(type);
if (n.isAssignAdd()) {
updateScopeForTypeChange(scope, left, leftType, type);
}
return scope;
}
private boolean isAddedAsNumber(JSType type) {
return type.isSubtype(registry.createUnionType(VOID_TYPE, NULL_TYPE,
NUMBER_VALUE_OR_OBJECT_TYPE, BOOLEAN_TYPE, BOOLEAN_OBJECT_TYPE));
}
private FlowScope traverseHook(Node n, FlowScope scope) {
Node condition = n.getFirstChild();
Node trueNode = condition.getNext();
Node falseNode = n.getLastChild();
// verify the condition
scope = traverse(condition, scope);
// reverse abstract interpret the condition to produce two new scopes
FlowScope trueScope = reverseInterpreter.
getPreciserScopeKnowingConditionOutcome(
condition, scope, true);
FlowScope falseScope = reverseInterpreter.
getPreciserScopeKnowingConditionOutcome(
condition, scope, false);
// traverse the true node with the trueScope
traverse(trueNode, trueScope.createChildFlowScope());
// traverse the false node with the falseScope
traverse(falseNode, falseScope.createChildFlowScope());
// meet true and false nodes' types and assign
JSType trueType = trueNode.getJSType();
JSType falseType = falseNode.getJSType();
if (trueType != null && falseType != null) {
n.setJSType(trueType
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>.getLeastSupertype(falseType));
} else {
n.setJSType(null);
}
return scope.createChildFlowScope();
}
private FlowScope traverseCall(Node n, FlowScope scope) {
scope = traverseChildren(n, scope);
Node left = n.getFirstChild();
JSType functionType = getJSType(left).restrictByNotNullOrUndefined();
if (functionType.isFunctionType()) {
FunctionType fnType = functionType.toMaybeFunctionType();
n.setJSType(fnType.getReturnType());
backwardsInferenceFromCallSite(n, fnType);
} else if (functionType.equals(getNativeType(CHECKED_UNKNOWN_TYPE))) {
n.setJSType(getNativeType(CHECKED_UNKNOWN_TYPE));
}
scope = tightenTypesAfterAssertions(scope, n);
return scope;
}
private FlowScope tightenTypesAfterAssertions(FlowScope scope,
Node callNode) {
Node left = callNode.getFirstChild();
Node firstParam = left.getNext();
AssertionFunctionSpec assertionFunctionSpec =
assertionFunctionsMap.get(left.getQualifiedName());
if (assertionFunctionSpec == null || firstParam == null) {
return scope;
}
Node assertedNode = assertionFunctionSpec.getAssertedParam(firstParam);
if (assertedNode == null) {
return scope;
}
JSType assertedType = assertionFunctionSpec.getAssertedType(
callNode, registry);
String assertedNodeName = assertedNode.getQualifiedName();
JSType narrowed;
// Handle assertions that enforce expressions evaluate to true.
if (assertedType == null) {
// Handle arbitrary expressions within the assert.
scope = reverseInterpreter.getPreciserScopeKnowingConditionOutcome(
assertedNode, scope, true);
// Build the result of the assertExpression
narrowed = getJSType(assertedNode).restrictByNotNullOrUndefined();
} else {
// Handle assertions that enforce expressions are of a certain type.
JSType type = getJSType(assertedNode);
narrowed = type.getGreatestSubtype(assertedType);
if (assertedNodeName != null && type.differsFrom(narrowed)) {
scope = narrowScope(scope, assertedNode, narrowed);
}
}
if (getJSType(callNode).differsFrom(narrowed)) {
callNode.setJSType(narrowed);
}
return scope;
}
private FlowScope narrowScope(FlowScope scope, Node node, JSType narrowed) {
if (node.isThis()) {
// "this" references don't need to be modeled in the control flow graph.
return scope;
}
scope = scope.createChildFlowScope();
if (node.isGetProp()) {
scope.inferQualifiedSlot(
node, node.getQualifiedName(), getJSType(node), narrowed);
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> } else {
redeclareSimpleVar(scope, node, narrowed);
}
return scope;
}
/**
* We only do forward type inference. We do not do full backwards
* type inference.
*
* In other words, if we have,
* <code>
* var x = f();
* g(x);
* </code>
* a forward type-inference engine would try to figure out the type
* of "x" from the return type of "f". A backwards type-inference engine
* would try to figure out the type of "x" from the parameter type of "g".
*
* However, there are a few special syntactic forms where we do some
* some half-assed backwards type-inference, because programmers
* expect it in this day and age. To take an example from Java,
* <code>
* List<String> x = Lists.newArrayList();
* </code>
* The Java compiler will be able to infer the generic type of the List
* returned by newArrayList().
*
* In much the same way, we do some special-case backwards inference for
* JS. Those cases are enumerated here.
*/
private void backwardsInferenceFromCallSite(Node n, FunctionType fnType) {
boolean updatedFnType = inferTemplatedTypesForCall(n, fnType);
if (updatedFnType) {
fnType = n.getFirstChild().getJSType().toMaybeFunctionType();
}
updateTypeOfParameters(n, fnType);
updateBind(n, fnType);
}
/**
* When "bind" is called on a function, we infer the type of the returned
* "bound" function by looking at the number of parameters in the call site.
*/
private void updateBind(Node n, FunctionType fnType) {
CodingConvention.Bind bind =
compiler.getCodingConvention().describeFunctionBind(n, true);
if (bind == null) {
return;
}
FunctionType callTargetFn = getJSType(bind.target)
.restrictByNotNullOrUndefined().toMaybeFunctionType();
if (callTargetFn == null) {
return;
}
n.setJSType(
callTargetFn.getBindReturnType(
// getBindReturnType expects the 'this' argument to be included.
bind.getBoundParameterCount() + 1));
}
/**
* For functions with function parameters, type inference will set the type of
* a function literal argument from the function parameter type.
*/
private void updateTypeOfParameters(Node n, FunctionType fnType) {
int i = 0;
int childCount = n.getChildCount();
for (Node iParameter : fnType.getParameters()) {
if (i + 1 >= childCount) {
// TypeCheck#visitParametersList will warn so we bail.
return;
}
JSType iParameterType = getJSType(iParameter);
Node iArgument = n
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>.getChildAtIndex(i + 1);
JSType iArgumentType = getJSType(iArgument);
inferPropertyTypesToMatchConstraint(iArgumentType, iParameterType);
// TODO(johnlenz): Filter out non-function types
// (such as null and undefined) as
// we only care about FUNCTION subtypes here.
JSType restrictedParameter = iParameterType
.restrictByNotNullOrUndefined()
.toMaybeFunctionType();
if (restrictedParameter != null) {
if (iArgument.isFunction() &&
iArgumentType.isFunctionType() &&
iArgument.getJSDocInfo() == null) {
iArgument.setJSType(restrictedParameter);
}
}
i++;
}
}
private Map<TemplateType, JSType> inferTemplateTypesFromParameters(
FunctionType fnType, Node call) {
if (fnType.getTemplateTypeNames().isEmpty()
|| !call.hasMoreThanOneChild()) {
return Collections.emptyMap();
}
Map<TemplateType, JSType> resolvedTypes = Maps.newIdentityHashMap();
maybeResolveTemplateTypeFromNodes(
fnType.getParameters(),
call.getChildAtIndex(1).siblings(),
resolvedTypes);
return resolvedTypes;
}
private void maybeResolveTemplatedType(
JSType paramType,
JSType argType,
Map<TemplateType, JSType> resolvedTypes) {
if (paramType.isTemplateType()) {
// @param {T}
resolvedTemplateType(
resolvedTypes, paramType.toMaybeTemplateType(), argType);
} else if (paramType.isUnionType()) {
// @param {Array.<T>|NodeList|Arguments|{length:number}}
UnionType unionType = paramType.toMaybeUnionType();
for (JSType alernative : unionType.getAlternates()) {
maybeResolveTemplatedType(alernative, argType, resolvedTypes);
}
} else if (paramType.isFunctionType()) {
FunctionType paramFunctionType = paramType.toMaybeFunctionType();
FunctionType argFunctionType = argType
.restrictByNotNullOrUndefined()
.collapseUnion()
.toMaybeFunctionType();
if (argFunctionType != null && argFunctionType.isSubtype(paramType)) {
// infer from return type of the function type
maybeResolveTemplatedType(
paramFunctionType.getReturnType(),
argFunctionType.getReturnType(), resolvedTypes);
// infer from parameter types of the function type
maybeResolveTemplateTypeFromNodes(
paramFunctionType.getParameters(),
argFunctionType.getParameters(), resolvedTypes);
}
} else if (paramType.isParameterizedType()) {
ParameterizedType paramObjectType = paramType.toMaybeParameterizedType();
JSType typeParameter = paramObjectType.getParameterType();
Preconditions.checkNotNull(typeParameter);
if (typeParameter != null) {
// @param {Array.<T>}
ObjectType argObjectType = argType
.restrictByNotNullOrUndefined()
.collapseUnion()
.toMaybeParameterizedType();
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>Type(replacementFnType);
n.setJSType(replacementFnType.getReturnType());
return true;
}
return false;
}
private FlowScope traverseNew(Node n, FlowScope scope) {
scope = traverseChildren(n, scope);
Node constructor = n.getFirstChild();
JSType constructorType = constructor.getJSType();
JSType type = null;
if (constructorType != null) {
constructorType = constructorType.restrictByNotNullOrUndefined();
if (constructorType.isUnknownType()) {
type = getNativeType(UNKNOWN_TYPE);
} else {
FunctionType ct = constructorType.toMaybeFunctionType();
if (ct == null && constructorType instanceof FunctionType) {
// If constructorType is a NoObjectType, then toMaybeFunctionType will
// return null. But NoObjectType implements the FunctionType
// interface, precisely because it can validly construct objects.
ct = (FunctionType) constructorType;
}
if (ct != null && ct.isConstructor()) {
type = ct.getInstanceType();
backwardsInferenceFromCallSite(n, ct);
}
}
}
n.setJSType(type);
return scope;
}
private BooleanOutcomePair traverseAnd(Node n, FlowScope scope) {
return traverseShortCircuitingBinOp(n, scope, true);
}
private FlowScope traverseChildren(Node n, FlowScope scope) {
for (Node el = n.getFirstChild(); el != null; el = el.getNext()) {
scope = traverse(el, scope);
}
return scope;
}
private FlowScope traverseGetElem(Node n, FlowScope scope) {
scope = traverseChildren(n, scope);
ObjectType objType = ObjectType.cast(
getJSType(n.getFirstChild()).restrictByNotNullOrUndefined());
if (objType != null) {
JSType type = objType.getParameterType();
if (type != null) {
n.setJSType(type);
}
}
return dereferencePointer(n.getFirstChild(), scope);
}
private FlowScope traverseGetProp(Node n, FlowScope scope) {
Node objNode = n.getFirstChild();
Node property = n.getLastChild();
scope = traverseChildren(n, scope);
n.setJSType(
getPropertyType(
objNode.getJSType(), property.getString(), n, scope));
return dereferencePointer(n.getFirstChild(), scope);
}
/**
* Suppose X is an object with inferred properties.
* Suppose also that X is used in a way where it would only type-check
* correctly if some of those properties are widened.
* Then we should be polite and automatically widen X's properties for him.
*
* For a concrete example, consider:
* param x {{prop: (number|undefined)}}
* function f(x) {}
* f({});
*
* If we give the
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> anonymous object an inferred property of (number|undefined),
* then this code will type-check appropriately.
*/
private void inferPropertyTypesToMatchConstraint(
JSType type, JSType constraint) {
if (type == null || constraint == null) {
return;
}
ObjectType constraintObj =
ObjectType.cast(constraint.restrictByNotNullOrUndefined());
if (constraintObj != null) {
type.matchConstraint(constraintObj);
}
}
/**
* If we access a property of a symbol, then that symbol is not
* null or undefined.
*/
private FlowScope dereferencePointer(Node n, FlowScope scope) {
if (n.isQualifiedName()) {
JSType type = getJSType(n);
JSType narrowed = type.restrictByNotNullOrUndefined();
if (type != narrowed) {
scope = narrowScope(scope, n, narrowed);
}
}
return scope;
}
private JSType getPropertyType(JSType objType, String propName,
Node n, FlowScope scope) {
// We often have a couple of different types to choose from for the
// property. Ordered by accuracy, we have
// 1) A locally inferred qualified name (which is in the FlowScope)
// 2) A globally declared qualified name (which is in the FlowScope)
// 3) A property on the owner type (which is on objType)
// 4) A name in the type registry (as a last resort)
JSType unknownType = getNativeType(UNKNOWN_TYPE);
JSType propertyType = null;
boolean isLocallyInferred = false;
// Scopes sometimes contain inferred type info about qualified names.
String qualifiedName = n.getQualifiedName();
StaticSlot<JSType> var = scope.getSlot(qualifiedName);
if (var != null) {
JSType varType = var.getType();
if (varType != null) {
boolean isDeclared = !var.isTypeInferred();
isLocallyInferred = (var != syntacticScope.getSlot(qualifiedName));
if (isDeclared || isLocallyInferred) {
propertyType = varType;
}
}
}
if (propertyType == null && objType != null) {
JSType foundType = objType.findPropertyType(propName);
if (foundType != null) {
propertyType = foundType;
}
}
if ((propertyType == null || propertyType.isUnknownType())
&& qualifiedName != null) {
// If we find this node in the registry, then we can infer its type.
ObjectType regType = ObjectType.cast(registry.getType(qualifiedName));
if (regType != null) {
propertyType = regType.getConstructor();
}
}
if (propertyType == null) {
return getNativeType(UNKNOWN_TYPE);
} else if (propertyType.equals(unknownType) && is
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>LocallyInferred) {
// If the type has been checked in this scope,
// then use CHECKED_UNKNOWN_TYPE instead to indicate that.
return getNativeType(CHECKED_UNKNOWN_TYPE);
} else {
return propertyType;
}
}
private BooleanOutcomePair traverseOr(Node n, FlowScope scope) {
return traverseShortCircuitingBinOp(n, scope, false);
}
private BooleanOutcomePair traverseShortCircuitingBinOp(
Node n, FlowScope scope, boolean condition) {
Node left = n.getFirstChild();
Node right = n.getLastChild();
// type the left node
BooleanOutcomePair leftLiterals =
traverseWithinShortCircuitingBinOp(left,
scope.createChildFlowScope());
JSType leftType = left.getJSType();
// reverse abstract interpret the left node to produce the correct
// scope in which to verify the right node
FlowScope rightScope = reverseInterpreter.
getPreciserScopeKnowingConditionOutcome(
left, leftLiterals.getOutcomeFlowScope(left.getType(), condition),
condition);
// type the right node
BooleanOutcomePair rightLiterals =
traverseWithinShortCircuitingBinOp(
right, rightScope.createChildFlowScope());
JSType rightType = right.getJSType();
JSType type;
BooleanOutcomePair literals;
if (leftType != null && rightType != null) {
leftType = leftType.getRestrictedTypeGivenToBooleanOutcome(!condition);
if (leftLiterals.toBooleanOutcomes ==
BooleanLiteralSet.get(!condition)) {
// Use the restricted left type, since the right side never gets
// evaluated.
type = leftType;
literals = leftLiterals;
} else {
// Use the join of the restricted left type knowing the outcome of the
// ToBoolean predicate and of the right type.
type = leftType.getLeastSupertype(rightType);
literals =
getBooleanOutcomePair(leftLiterals, rightLiterals, condition);
}
// Exclude the boolean type if the literal set is empty because a boolean
// can never actually be returned.
if (literals.booleanValues == BooleanLiteralSet.EMPTY &&
getNativeType(BOOLEAN_TYPE).isSubtype(type)) {
// Exclusion only make sense for a union type.
if (type.isUnionType()) {
type = type.toMaybeUnionType().getRestrictedUnion(
getNativeType(BOOLEAN_TYPE));
}
}
} else {
type = null;
literals = new BooleanOutcomePair(
BooleanLiteralSet.BOTH, BooleanLiteralSet.BOTH,
leftLiterals.getJoinedFlowScope(),
rightLiterals.getJoinedFlowScope());
}
n.setJSType(type);
return literals;
}
private BooleanOutcomePair traverseWithinShortCircuitingBinOp(Node n,
FlowScope scope) {
switch (n.getType()) {
case Token.AND:
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>
return traverseAnd(n, scope);
case Token.OR:
return traverseOr(n, scope);
default:
scope = traverse(n, scope);
return newBooleanOutcomePair(n.getJSType(), scope);
}
}
/**
* Infers the boolean outcome pair that can be taken by a
* short-circuiting binary operation ({@code &&} or {@code ||}).
* @see #getBooleanOutcomes(BooleanLiteralSet, BooleanLiteralSet, boolean)
*/
BooleanOutcomePair getBooleanOutcomePair(BooleanOutcomePair left,
BooleanOutcomePair right, boolean condition) {
return new BooleanOutcomePair(
getBooleanOutcomes(left.toBooleanOutcomes, right.toBooleanOutcomes,
condition),
getBooleanOutcomes(left.booleanValues, right.booleanValues, condition),
left.getJoinedFlowScope(), right.getJoinedFlowScope());
}
/**
* Infers the boolean literal set that can be taken by a
* short-circuiting binary operation ({@code &&} or {@code ||}).
* @param left the set of possible {@code ToBoolean} predicate results for
* the expression on the left side of the operator
* @param right the set of possible {@code ToBoolean} predicate results for
* the expression on the right side of the operator
* @param condition the left side {@code ToBoolean} predicate result that
* causes the right side to get evaluated (i.e. not short-circuited)
* @return a set of possible {@code ToBoolean} predicate results for the
* entire expression
*/
static BooleanLiteralSet getBooleanOutcomes(BooleanLiteralSet left,
BooleanLiteralSet right, boolean condition) {
return right.union(left.intersection(BooleanLiteralSet.get(!condition)));
}
/**
* When traversing short-circuiting binary operations, we need to keep track
* of two sets of boolean literals:
* 1. {@code toBooleanOutcomes}: boolean literals as converted from any types,
* 2. {@code booleanValues}: boolean literals from just boolean types.
*/
private final class BooleanOutcomePair {
final BooleanLiteralSet toBooleanOutcomes;
final BooleanLiteralSet booleanValues;
// The scope if only half of the expression executed, when applicable.
final FlowScope leftScope;
// The scope when the whole expression executed.
final FlowScope rightScope;
// The scope when we don't know how much of the expression is executed.
FlowScope joinedScope = null;
BooleanOutcomePair(
BooleanLiteralSet toBooleanOutcomes, BooleanLiteralSet booleanValues,
FlowScope leftScope, FlowScope rightScope) {
this.toBooleanOutcomes = toBooleanOutcomes;
this.booleanValues = booleanValues;
this.leftScope = leftScope;
this.rightScope = rightScope;
}
/**
* Gets the safe estimated scope without knowing if all of the
* subexpressions will be evaluated.
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>
*/
FlowScope getJoinedFlowScope() {
if (joinedScope == null) {
if (leftScope == rightScope) {
joinedScope = rightScope;
} else {
joinedScope = join(leftScope, rightScope);
}
}
return joinedScope;
}
/**
* Gets the outcome scope if we do know the outcome of the entire
* expression.
*/
FlowScope getOutcomeFlowScope(int nodeType, boolean outcome) {
if (nodeType == Token.AND && outcome ||
nodeType == Token.OR && !outcome) {
// We know that the whole expression must have executed.
return rightScope;
} else {
return getJoinedFlowScope();
}
}
}
private BooleanOutcomePair newBooleanOutcomePair(
JSType jsType, FlowScope flowScope) {
if (jsType == null) {
return new BooleanOutcomePair(
BooleanLiteralSet.BOTH, BooleanLiteralSet.BOTH, flowScope, flowScope);
}
return new BooleanOutcomePair(jsType.getPossibleToBooleanOutcomes(),
registry.getNativeType(BOOLEAN_TYPE).isSubtype(jsType) ?
BooleanLiteralSet.BOTH : BooleanLiteralSet.EMPTY,
flowScope, flowScope);
}
private void redeclareSimpleVar(
FlowScope scope, Node nameNode, JSType varType) {
Preconditions.checkState(nameNode.isName());
String varName = nameNode.getString();
if (varType == null) {
varType = getNativeType(JSTypeNative.UNKNOWN_TYPE);
}
if (isUnflowable(syntacticScope.getVar(varName))) {
return;
}
scope.inferSlotType(varName, varType);
}
private boolean isUnflowable(Var v) {
return v != null && v.isLocal() && v.isMarkedEscaped() &&
// It's OK to flow a variable in the scope where it's escaped.
v.getScope() == syntacticScope;
}
/**
* This method gets the JSType from the Node argument and verifies that it is
* present.
*/
private JSType getJSType(Node n) {
JSType jsType = n.getJSType();
if (jsType == null) {
// TODO(nicksantos): This branch indicates a compiler bug, not worthy of
// halting the compilation but we should log this and analyze to track
// down why it happens. This is not critical and will be resolved over
// time as the type checker is extended.
return getNativeType(UNKNOWN_TYPE);
} else {
return jsType;
}
}
private JSType getNativeType(JSTypeNative typeId) {
return registry.getNativeType(typeId);
}
}
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> null || NodeUtil.isControlStructure(parent)
|| NodeUtil.isStatementBlock(parent);
}
}
/**
* Abstract callback to visit a pruned set of nodes.
*/
public abstract static class AbstractNodeTypePruningCallback
implements Callback {
private final Set<Integer> nodeTypes;
private final boolean include;
/**
* Creates an abstract pruned callback.
* @param nodeTypes the nodes to include in the traversal
*/
public AbstractNodeTypePruningCallback(Set<Integer> nodeTypes) {
this(nodeTypes, true);
}
/**
* Creates an abstract pruned callback.
* @param nodeTypes the nodes to include/exclude in the traversal
* @param include whether to include or exclude the nodes in the traversal
*/
public AbstractNodeTypePruningCallback(Set<Integer> nodeTypes,
boolean include) {
this.nodeTypes = nodeTypes;
this.include = include;
}
@Override
public boolean shouldTraverse(NodeTraversal nodeTraversal, Node n,
Node parent) {
return include == nodeTypes.contains(n.getType());
}
}
/**
* Creates a node traversal using the specified callback interface.
*/
public NodeTraversal(AbstractCompiler compiler, Callback cb) {
this(compiler, cb, new SyntacticScopeCreator(compiler));
}
/**
* Creates a node traversal using the specified callback interface
* and the scope creator.
*/
public NodeTraversal(AbstractCompiler compiler, Callback cb,
ScopeCreator scopeCreator) {
this.callback = cb;
if (cb instanceof ScopedCallback) {
this.scopeCallback = (ScopedCallback) cb;
}
this.compiler = compiler;
this.inputId = null;
this.sourceName = "";
this.scopeCreator = scopeCreator;
}
private void throwUnexpectedException(Exception unexpectedException) {
// If there's an unexpected exception, try to get the
// line number of the code that caused it.
String message = unexpectedException.getMessage();
// TODO(user): It is possible to get more information if curNode or
// its parent is missing. We still have the scope stack in which it is still
// very useful to find out at least which function caused the exception.
if (inputId != null) {
message =
unexpectedException.getMessage() + "\n" +
formatNodeContext("Node", curNode) +
(curNode == null ?
"" :
formatNodeContext("Parent", curNode.getParent()));
}
compiler.throwInternalError(message, unexpectedException);
}
private String formatNodeContext(String label, Node n) {
if (n == null) {
return " " + label + ": NULL";
}
return " " + label + "(" + n.toString(false, false, false) + "): "
+ formatNodePosition(n);
}
/**
* Traverses a parse tree recursively.
*/
public void
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> * Traverses a branch.
*/
@SuppressWarnings("fallthrough")
private void traverseBranch(Node n, Node parent) {
int type = n.getType();
if (type == Token.SCRIPT) {
inputId = n.getInputId();
sourceName = getSourceName(n);
}
curNode = n;
if (!callback.shouldTraverse(this, n, parent)) return;
switch (type) {
case Token.FUNCTION:
traverseFunction(n, parent);
break;
default:
for (Node child = n.getFirstChild(); child != null; ) {
// child could be replaced, in which case our child node
// would no longer point to the true next
Node next = child.getNext();
traverseBranch(child, n);
child = next;
}
break;
}
curNode = n;
callback.visit(this, n, parent);
}
/**
* Traverses a function.
*/
private void traverseFunction(Node n, Node parent) {
Preconditions.checkState(n.getChildCount() == 3);
Preconditions.checkState(n.isFunction());
final Node fnName = n.getFirstChild();
boolean isFunctionExpression = (parent != null)
&& NodeUtil.isFunctionExpression(n);
if (!isFunctionExpression) {
// Functions declarations are in the scope containing the declaration.
traverseBranch(fnName, n);
}
curNode = n;
pushScope(n);
if (isFunctionExpression) {
// Function expression names are only accessible within the function
// scope.
traverseBranch(fnName, n);
}
final Node args = fnName.getNext();
final Node body = args.getNext();
// Args
traverseBranch(args, n);
// Body
Preconditions.checkState(body.getNext() == null &&
body.isBlock(), body);
traverseBranch(body, n);
popScope();
}
/** Examines the functions stack for the last instance of a function node. */
@SuppressWarnings("unchecked")
public Node getEnclosingFunction() {
if (scopes.size() + scopeRoots.size() < 2) {
return null;
} else {
if (scopeRoots.isEmpty()) {
return scopes.peek().getRootNode();
} else {
return scopeRoots.peek();
}
}
}
/** Creates a new scope (e.g. when entering a function). */
private void pushScope(Node node) {
Preconditions.checkState(curNode != null);
scopeRoots.push(node);
cfgs.push(null);
if (scopeCallback != null) {
scopeCallback.enterScope(this);
}
}
/** Creates a new scope (e.g. when entering a function). */
private void pushScope(Scope s) {
Preconditions.checkState(curNode != null);
scopes.push(s);
cfgs.push(null);
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> if (scopeCallback != null) {
scopeCallback.enterScope(this);
}
}
/** Pops back to the previous scope (e.g. when leaving a function). */
private void popScope() {
if (scopeCallback != null) {
scopeCallback.exitScope(this);
}
if (scopeRoots.isEmpty()) {
scopes.pop();
} else {
scopeRoots.pop();
}
cfgs.pop();
}
/** Gets the current scope. */
public Scope getScope() {
Scope scope = scopes.isEmpty() ? null : scopes.peek();
if (scopeRoots.isEmpty()) {
return scope;
}
Iterator<Node> it = scopeRoots.descendingIterator();
while (it.hasNext()) {
scope = scopeCreator.createScope(it.next(), scope);
scopes.push(scope);
}
scopeRoots.clear();
return scope;
}
/** Gets the control flow graph for the current JS scope. */
public ControlFlowGraph<Node> getControlFlowGraph() {
if (cfgs.peek() == null) {
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, true);
cfa.process(null, getScopeRoot());
cfgs.pop();
cfgs.push(cfa.getCfg());
}
return cfgs.peek();
}
/** Returns the current scope's root. */
public Node getScopeRoot() {
if (scopeRoots.isEmpty()) {
return scopes.peek().getRootNode();
} else {
return scopeRoots.peek();
}
}
/**
* Determines whether the traversal is currently in the global scope.
*/
boolean inGlobalScope() {
return getScopeDepth() <= 1;
}
int getScopeDepth() {
return scopes.size() + scopeRoots.size();
}
public boolean hasScope() {
return !(scopes.isEmpty() && scopeRoots.isEmpty());
}
/** Reports a diagnostic (error or warning) */
public void report(Node n, DiagnosticType diagnosticType,
String... arguments) {
JSError error = JSError.make(getSourceName(), n, diagnosticType, arguments);
compiler.report(error);
}
private static String getSourceName(Node n) {
String name = n.getSourceFileName();
return name == null ? "" : name;
}
InputId getInputId() {
return inputId;
}
/**
* Creates a JSError during NodeTraversal.
*
* @param n Determines the line and char position within the source file name
* @param type The DiagnosticType
* @param arguments Arguments to be incorporated into the message
*/
public JSError makeError(Node n, CheckLevel level, DiagnosticType type,
String... arguments) {
return JSError.make(getSourceName(), n, level, type, arguments);
}
/**
* Creates a JSError during NodeTraversal.
*
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> static Node propdef(Node string, Node value) {
Preconditions.checkState(string.isStringKey());
Preconditions.checkState(!string.hasChildren());
Preconditions.checkState(mayBeExpression(value));
string.addChildToFront(value);
return string;
}
public static Node arraylit(Node ... exprs) {
Node arraylit = new Node(Token.ARRAYLIT);
for (Node expr : exprs) {
Preconditions.checkState(mayBeExpressionOrEmpty(expr));
arraylit.addChildToBack(expr);
}
return arraylit;
}
public static Node regexp(Node expr) {
Preconditions.checkState(expr.isString());
return new Node(Token.REGEXP, expr);
}
public static Node regexp(Node expr, Node flags) {
Preconditions.checkState(expr.isString());
Preconditions.checkState(flags.isString());
return new Node(Token.REGEXP, expr, flags);
}
public static Node string(String s) {
return Node.newString(s);
}
public static Node stringKey(String s) {
return Node.newString(Token.STRING_KEY, s);
}
public static Node number(double d) {
return Node.newNumber(d);
}
public static Node thisNode() {
return new Node(Token.THIS);
}
public static Node trueNode() {
return new Node(Token.TRUE);
}
public static Node falseNode() {
return new Node(Token.FALSE);
}
public static Node nullNode() {
return new Node(Token.NULL);
}
// helper methods
private static Node binaryOp(int token, Node expr1, Node expr2) {
Preconditions.checkState(mayBeExpression(expr1));
Preconditions.checkState(mayBeExpression(expr2));
return new Node(token, expr1, expr2);
}
private static Node unaryOp(int token, Node expr) {
Preconditions.checkState(mayBeExpression(expr));
return new Node(token, expr);
}
private static boolean mayBeExpressionOrEmpty(Node n) {
return n.isEmpty() || mayBeExpression(n);
}
private static boolean isAssignmentTarget(Node n) {
return n.isName() || n.isGetProp() || n.isGetElem();
}
// NOTE: some nodes are neither statements nor expression nodes:
// SCRIPT, LABEL_NAME, PARAM_LIST, CASE, DEFAULT_CASE, CATCH
// GETTER_DEF, SETTER_DEF
/**
* It isn't possible to always determine if a detached node is a expression,
* so make a best guess.
*/
private static boolean mayBeStatementNoReturn(Node n) {
switch (n.getType()) {
case Token.EMPTY:
case Token.FUNCTION:
// EMPTY and FUNCTION are used both in expression and statement
// contexts
return true;
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> case Token.BLOCK:
case Token.BREAK:
case Token.CONST:
case Token.CONTINUE:
case Token.DEBUGGER:
case Token.DO:
case Token.EXPR_RESULT:
case Token.FOR:
case Token.IF:
case Token.LABEL:
case Token.SWITCH:
case Token.THROW:
case Token.TRY:
case Token.VAR:
case Token.WHILE:
case Token.WITH:
return true;
default:
return false;
}
}
/**
* It isn't possible to always determine if a detached node is a expression,
* so make a best guess.
*/
private static boolean mayBeStatement(Node n) {
if (!mayBeStatementNoReturn(n)) {
return n.isReturn();
}
return true;
}
/**
* It isn't possible to always determine if a detached node is a expression,
* so make a best guess.
*/
private static boolean mayBeExpression(Node n) {
switch (n.getType()) {
case Token.FUNCTION:
// FUNCTION is used both in expression and statement
// contexts.
return true;
case Token.ADD:
case Token.AND:
case Token.ARRAYLIT:
case Token.ASSIGN:
case Token.ASSIGN_BITOR:
case Token.ASSIGN_BITXOR:
case Token.ASSIGN_BITAND:
case Token.ASSIGN_LSH:
case Token.ASSIGN_RSH:
case Token.ASSIGN_URSH:
case Token.ASSIGN_ADD:
case Token.ASSIGN_SUB:
case Token.ASSIGN_MUL:
case Token.ASSIGN_DIV:
case Token.ASSIGN_MOD:
case Token.BITAND:
case Token.BITOR:
case Token.BITNOT:
case Token.BITXOR:
case Token.CALL:
case Token.COMMA:
case Token.DEC:
case Token.DELPROP:
case Token.DIV:
case Token.EQ:
case Token.FALSE:
case Token.GE:
case Token.GETPROP:
case Token.GETELEM:
case Token.GT:
case Token.HOOK:
case Token.IN:
case Token.INC:
case Token.INSTANCEOF:
case Token.LE:
case Token.LSH:
case Token.LT:
case Token.MOD:
case Token.MUL:
case Token.NAME:
case Token.NE:
case Token.NEG:
case Token.NEW:
case Token.NOT:
case Token.NUMBER:
case Token.NULL:
case Token.OBJECTLIT:
case Token.OR:
case Token.POS:
case Token.REGEXP:
case Token.RSH:
case Token.SHEQ:
case Token.SHNE:
case Token.STRING:
case Token.SUB:
case Token.THIS:
case Token.TYPEOF:
case Token.TRUE:
case Token
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> (expr.isOptionalArg() || expr.isVarArgs()) {
return expr;
} else {
return new JSTypeExpression(
new Node(Token.EQUALS, expr.root), expr.sourceName);
}
}
/**
* @return Whether this expression denotes an optional {@code @param}.
*/
public boolean isOptionalArg() {
return root.getType() == Token.EQUALS;
}
/**
* @return Whether this expression denotes a rest args {@code @param}.
*/
public boolean isVarArgs() {
return root.getType() == Token.ELLIPSIS;
}
/**
* Evaluates the type expression into a {@code JSType} object.
*/
public JSType evaluate(StaticScope<JSType> scope, JSTypeRegistry registry) {
JSType type = registry.createFromTypeNodes(root, sourceName, scope);
root.setJSType(type);
return type;
}
@Override
public boolean equals(Object other) {
return other instanceof JSTypeExpression &&
((JSTypeExpression) other).root.isEquivalentTo(root);
}
@Override
public int hashCode() {
return root.toStringTree().hashCode();
}
/**
* @return The source for this type expression. Note that it will not
* contain an expression if there's an @override tag.
*/
public Node getRoot() {
return root;
}
}
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> = new NodeTraversal(
compiler, new TypeInferringCallback(), scopeCreator);
inferTypes.traverseWithScope(node, topScope);
}
void inferTypes(NodeTraversal t, Node n, Scope scope) {
TypeInference typeInference =
new TypeInference(
compiler, computeCfg(n), reverseInterpreter, scope,
assertionFunctionsMap);
try {
typeInference.analyze();
// Resolve any new type names found during the inference.
compiler.getTypeRegistry().resolveTypesInScope(scope);
} catch (DataFlowAnalysis.MaxIterationsExceededException e) {
compiler.report(t.makeError(n, DATAFLOW_ERROR));
}
}
private class TypeInferringCallback extends AbstractScopedCallback {
@Override
public void enterScope(NodeTraversal t) {
inferTypes(t, t.getCurrentNode(), t.getScope());
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
// Do nothing
}
}
private ControlFlowGraph<Node> computeCfg(Node n) {
ControlFlowAnalysis cfa = new ControlFlowAnalysis(compiler, false, false);
cfa.process(null, n);
return cfa.getCfg();
}
}
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>1;
this.parent = directParent;
}
}
LinkedFlowScope(FlatFlowScopeCache cache) {
this(cache, null);
}
LinkedFlowScope(LinkedFlowScope directParent) {
this(directParent.cache, directParent);
}
/** Gets the function scope for this flow scope. */
private Scope getFunctionScope() {
return cache.functionScope;
}
/** Whether this flows from a bottom scope. */
private boolean flowsFromBottom() {
return getFunctionScope().isBottom();
}
/**
* Creates an entry lattice for the flow.
*/
public static LinkedFlowScope createEntryLattice(Scope scope) {
return new LinkedFlowScope(new FlatFlowScopeCache(scope));
}
@Override
public void inferSlotType(String symbol, JSType type) {
Preconditions.checkState(!frozen);
lastSlot = new LinkedFlowSlot(symbol, type, lastSlot);
depth++;
cache.dirtySymbols.add(symbol);
}
@Override
public void inferQualifiedSlot(Node node, String symbol, JSType bottomType,
JSType inferredType) {
Scope functionScope = getFunctionScope();
if (functionScope.isLocal()) {
if (functionScope.getVar(symbol) == null && !functionScope.isBottom()) {
functionScope.declare(symbol, node, bottomType, null);
}
inferSlotType(symbol, inferredType);
}
}
@Override
public JSType getTypeOfThis() {
return cache.functionScope.getTypeOfThis();
}
@Override
public Node getRootNode() {
return getFunctionScope().getRootNode();
}
@Override
public StaticScope<JSType> getParentScope() {
return getFunctionScope().getParentScope();
}
/**
* Get the slot for the given symbol.
*/
@Override
public StaticSlot<JSType> getSlot(String name) {
if (cache.dirtySymbols.contains(name)) {
for (LinkedFlowSlot slot = lastSlot;
slot != null; slot = slot.parent) {
if (slot.getName().equals(name)) {
return slot;
}
}
}
return cache.getSlot(name);
}
@Override
public StaticSlot<JSType> getOwnSlot(String name) {
throw new UnsupportedOperationException();
}
@Override
public FlowScope createChildFlowScope() {
frozen = true;
if (depth > MAX_DEPTH) {
if (flattened == null) {
flattened = new FlatFlowScopeCache(this);
}
return new LinkedFlowScope(flattened);
}
return new LinkedFlowScope(this);
}
/**
* Iterate through all the linked flow scopes before this one.
* If there's one and only one slot defined between this scope
* and the blind scope, return it.
*/
@Override
public StaticSlot<JSType> findUniqueRefinedSlot
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>(FlowScope blindScope) {
StaticSlot<JSType> result = null;
for (LinkedFlowScope currentScope = this;
currentScope != blindScope;
currentScope = currentScope.parent) {
for (LinkedFlowSlot currentSlot = currentScope.lastSlot;
currentSlot != null &&
(currentScope.parent == null ||
currentScope.parent.lastSlot != currentSlot);
currentSlot = currentSlot.parent) {
if (result == null) {
result = currentSlot;
} else if (!currentSlot.getName().equals(result.getName())) {
return null;
}
}
}
return result;
}
/**
* Look through the given scope, and try to find slots where it doesn't
* have enough type information. Then fill in that type information
* with stuff that we've inferred in the local flow.
*/
@Override
public void completeScope(StaticScope<JSType> staticScope) {
Scope scope = (Scope) staticScope;
for (Iterator<Var> it = scope.getVars(); it.hasNext();) {
Var var = it.next();
if (var.isTypeInferred()) {
JSType type = var.getType();
if (type == null || type.isUnknownType()) {
JSType flowType = getSlot(var.getName()).getType();
var.setType(flowType);
}
}
}
}
/**
* Remove flow scopes that add nothing to the flow.
*/
// NOTE(nicksantos): This function breaks findUniqueRefinedSlot, because
// findUniqueRefinedSlot assumes that this scope is a direct descendant
// of blindScope. This is not necessarily true if this scope has been
// optimize()d and blindScope has not. This should be fixed. For now,
// we only use optimize() where we know that we won't have to do
// a findUniqueRefinedSlot on it.
@Override
public LinkedFlowScope optimize() {
LinkedFlowScope current;
for (current = this;
current.parent != null &&
current.lastSlot == current.parent.lastSlot;
current = current.parent) {}
return current;
}
/** Join the two FlowScopes. */
static class FlowScopeJoinOp extends JoinOp.BinaryJoinOp<FlowScope> {
@SuppressWarnings("unchecked")
@Override
public FlowScope apply(FlowScope a, FlowScope b) {
// To join the two scopes, we have to
LinkedFlowScope linkedA = (LinkedFlowScope) a;
LinkedFlowScope linkedB = (LinkedFlowScope) b;
linkedA.frozen = true;
linkedB.frozen = true;
if (linkedA.optimize() == linkedB.optimize()) {
return linkedA.createChildFlowScope();
}
return new LinkedFlowScope(new FlatFlowScopeCache(linkedA, linkedB));
}
}
@Override
public boolean equals(Object
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> other) {
if (other instanceof LinkedFlowScope) {
LinkedFlowScope that = (LinkedFlowScope) other;
if (this.optimize() == that.optimize()) {
return true;
}
// If two flow scopes are in the same function, then they could have
// two possible function scopes: the real one and the BOTTOM scope.
// If they have different function scopes, we *should* iterate through all
// the variables in each scope and compare. However, 99.9% of the time,
// they're not equal. And the other .1% of the time, we can pretend
// they're equal--this just means that data flow analysis will have
// to propagate the entry lattice a little bit further than it
// really needs to. Everything will still come out ok.
if (this.getFunctionScope() != that.getFunctionScope()) {
return false;
}
if (cache == that.cache) {
// If the two flow scopes have the same cache, then we can check
// equality a lot faster: by just looking at the "dirty" elements
// in the cache, and comparing them in both scopes.
for (String name : cache.dirtySymbols) {
if (diffSlots(getSlot(name), that.getSlot(name))) {
return false;
}
}
return true;
}
Map<String, StaticSlot<JSType>> myFlowSlots = allFlowSlots();
Map<String, StaticSlot<JSType>> otherFlowSlots = that.allFlowSlots();
for (StaticSlot<JSType> slot : myFlowSlots.values()) {
if (diffSlots(slot, otherFlowSlots.get(slot.getName()))) {
return false;
}
otherFlowSlots.remove(slot.getName());
}
for (StaticSlot<JSType> slot : otherFlowSlots.values()) {
if (diffSlots(slot, myFlowSlots.get(slot.getName()))) {
return false;
}
}
return true;
}
return false;
}
/**
* Determines whether two slots are meaningfully different for the
* purposes of data flow analysis.
*/
private boolean diffSlots(StaticSlot<JSType> slotA,
StaticSlot<JSType> slotB) {
boolean aIsNull = slotA == null || slotA.getType() == null;
boolean bIsNull = slotB == null || slotB.getType() == null;
if (aIsNull && bIsNull) {
return false;
} else if (aIsNull ^ bIsNull) {
return true;
}
// Both slots and types must be non-null.
return slotA.getType().differsFrom(slotB.getType());
}
/**
* Gets all the symbols that have been defined before this point
* in the current flow. Does not return slots that have not changed during
* the flow.
*
* For example, consider the code:
* <code>
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>.allFlowSlots();
linkedEquivalent = directParent;
}
// A cache at the join of two scope chains.
FlatFlowScopeCache(LinkedFlowScope joinedScopeA,
LinkedFlowScope joinedScopeB) {
linkedEquivalent = null;
// Always prefer the "real" function scope to the faked-out
// bottom scope.
functionScope = joinedScopeA.flowsFromBottom() ?
joinedScopeB.getFunctionScope() : joinedScopeA.getFunctionScope();
Map<String, StaticSlot<JSType>> slotsA = joinedScopeA.allFlowSlots();
Map<String, StaticSlot<JSType>> slotsB = joinedScopeB.allFlowSlots();
symbols = slotsA;
// There are 5 different join cases:
// 1) The type is declared in joinedScopeA, not in joinedScopeB,
// and not in functionScope. Just use the one in A.
// 2) The type is declared in joinedScopeB, not in joinedScopeA,
// and not in functionScope. Just use the one in B.
// 3) The type is declared in functionScope and joinedScopeA, but
// not in joinedScopeB. Join the two types.
// 4) The type is declared in functionScope and joinedScopeB, but
// not in joinedScopeA. Join the two types.
// 5) The type is declared in joinedScopeA and joinedScopeB. Join
// the two types.
Set<String> symbolNames = Sets.newHashSet(symbols.keySet());
symbolNames.addAll(slotsB.keySet());
for (String name : symbolNames) {
StaticSlot<JSType> slotA = slotsA.get(name);
StaticSlot<JSType> slotB = slotsB.get(name);
JSType joinedType = null;
if (slotB == null || slotB.getType() == null) {
StaticSlot<JSType> fnSlot
= joinedScopeB.getFunctionScope().getSlot(name);
JSType fnSlotType = fnSlot == null ? null : fnSlot.getType();
if (fnSlotType == null) {
// Case #1 -- already inserted.
} else {
// Case #3
joinedType = slotA.getType().getLeastSupertype(fnSlotType);
}
} else if (slotA == null || slotA.getType() == null) {
StaticSlot<JSType> fnSlot
= joinedScopeA.getFunctionScope().getSlot(name);
JSType fnSlotType = fnSlot == null ? null : fnSlot.getType();
if (fnSlotType == null) {
// Case #2
symbols.put(name, slotB);
} else {
// Case #4
joinedType = slotB.getType().getLeastSupertype(fnSlotType);
}
} else {
// Case #5
joinedType =
slotA.getType().get
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>LeastSupertype(slotB.getType());
}
if (joinedType != null) {
symbols.put(name, new SimpleSlot(name, joinedType, true));
}
}
}
/**
* Get the slot for the given symbol.
*/
public StaticSlot<JSType> getSlot(String name) {
if (symbols.containsKey(name)) {
return symbols.get(name);
} else {
return functionScope.getSlot(name);
}
}
}
}
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>
public boolean matchesNumberContext() {
return true;
}
@Override
public boolean matchesObjectContext() {
return true;
}
@Override
public boolean matchesStringContext() {
return true;
}
@Override
public TernaryValue testForEquality(JSType that) {
return UNKNOWN;
}
@Override
public boolean isNullable() {
return true;
}
@Override
public boolean isSubtype(JSType that) {
return true;
}
@Override
public <T> T visit(Visitor<T> visitor) {
return visitor.caseUnknownType();
}
@Override
String toStringHelper(boolean forAnnotations) {
return getReferenceName();
}
@Override
boolean defineProperty(String propertyName, JSType type,
boolean inferred, Node propertyNode) {
// nothing to define
return true;
}
@Override
public ObjectType getImplicitPrototype() {
return null;
}
@Override
public int getPropertiesCount() {
return Integer.MAX_VALUE;
}
@Override
void collectPropertyNames(Set<String> props) {
}
@Override
public JSType getPropertyType(String propertyName) {
return this;
}
@Override
public boolean hasProperty(String propertyName) {
return true;
}
@Override
public FunctionType getConstructor() {
return null;
}
@Override
public String getReferenceName() {
return isChecked ? "??" : "?";
}
@Override
public String getDisplayName() {
return "Unknown";
}
@Override
public boolean hasDisplayName() {
return true;
}
@Override
public boolean isPropertyTypeDeclared(String propertyName) {
return false;
}
@Override
public boolean isPropertyTypeInferred(String propertyName) {
return false;
}
@Override
public BooleanLiteralSet getPossibleToBooleanOutcomes() {
return BooleanLiteralSet.BOTH;
}
@Override
JSType resolveInternal(ErrorReporter t, StaticScope<JSType> scope) {
return this;
}
}
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> need to be declared in externs. These will be declared
// at the end of the pass, or when we see the equivalent var declared
// in the normal code.
private final Set<String> varsToDeclareInExterns = Sets.newHashSet();
private final AbstractCompiler compiler;
// Whether this is the post-processing sanity check.
private final boolean sanityCheck;
// Whether extern checks emit error.
private final boolean strictExternCheck;
VarCheck(AbstractCompiler compiler) {
this(compiler, false);
}
VarCheck(AbstractCompiler compiler, boolean sanityCheck) {
this.compiler = compiler;
this.strictExternCheck = compiler.getErrorLevel(
JSError.make("", 0, 0, UNDEFINED_EXTERN_VAR_ERROR)) == CheckLevel.ERROR;
this.sanityCheck = sanityCheck;
}
@Override
public void process(Node externs, Node root) {
// Don't run externs-checking in sanity check mode. Normalization will
// remove duplicate VAR declarations, which will make
// externs look like they have assigns.
if (!sanityCheck) {
NodeTraversal.traverse(compiler, externs, new NameRefInExternsCheck());
}
NodeTraversal.traverseRoots(
compiler, Lists.newArrayList(externs, root), this);
for (String varName : varsToDeclareInExterns) {
createSynthesizedExternVar(varName);
}
}
@Override
public void hotSwapScript(Node scriptRoot, Node originalRoot) {
Preconditions.checkState(scriptRoot.isScript());
NodeTraversal t = new NodeTraversal(compiler, this);
// Note we use the global scope to prevent wrong "undefined-var errors" on
// variables that are defined in other JS files.
t.traverseWithScope(scriptRoot,
SyntacticScopeCreator.generateUntypedTopScope(compiler));
// TODO(bashir) Check if we need to createSynthesizedExternVar like process.
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (!n.isName()) {
return;
}
String varName = n.getString();
// Only a function can have an empty name.
if (varName.isEmpty()) {
Preconditions.checkState(parent.isFunction());
Preconditions.checkState(NodeUtil.isFunctionExpression(parent));
return;
}
// Check if this is a declaration for a var that has been declared
// elsewhere. If so, mark it as a duplicate.
if ((parent.isVar() ||
NodeUtil.isFunctionDeclaration(parent)) &&
varsToDeclareInExterns.contains(varName)) {
createSynthesizedExternVar(varName);
n.addSuppression("duplicate");
}
// Check that the var has been declared.
Scope scope = t.getScope();
Scope.Var var = scope.getVar(varName);
if (var == null
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>) {
if (NodeUtil.isFunctionExpression(parent)) {
// e.g. [ function foo() {} ], it's okay if "foo" isn't defined in the
// current scope.
} else {
// The extern checks are stricter, don't report a second error.
if (!strictExternCheck || !t.getInput().isExtern()) {
t.report(n, UNDEFINED_VAR_ERROR, varName);
}
if (sanityCheck) {
throw new IllegalStateException("Unexpected variable " + varName);
} else {
createSynthesizedExternVar(varName);
scope.getGlobalScope().declare(varName, n,
null, getSynthesizedExternsInput());
}
}
return;
}
CompilerInput currInput = t.getInput();
CompilerInput varInput = var.input;
if (currInput == varInput || currInput == null || varInput == null) {
// The variable was defined in the same file. This is fine.
return;
}
// Check module dependencies.
JSModule currModule = currInput.getModule();
JSModule varModule = varInput.getModule();
JSModuleGraph moduleGraph = compiler.getModuleGraph();
if (!sanityCheck &&
varModule != currModule && varModule != null && currModule != null) {
if (moduleGraph.dependsOn(currModule, varModule)) {
// The module dependency was properly declared.
} else {
if (scope.isGlobal()) {
if (moduleGraph.dependsOn(varModule, currModule)) {
// The variable reference violates a declared module dependency.
t.report(n, VIOLATED_MODULE_DEP_ERROR,
currModule.getName(), varModule.getName(), varName);
} else {
// The variable reference is between two modules that have no
// dependency relationship. This should probably be considered an
// error, but just issue a warning for now.
t.report(n, MISSING_MODULE_DEP_ERROR,
currModule.getName(), varModule.getName(), varName);
}
} else {
t.report(n, STRICT_MODULE_DEP_ERROR,
currModule.getName(), varModule.getName(), varName);
}
}
}
}
/**
* Create a new variable in a synthetic script. This will prevent
* subsequent compiler passes from crashing.
*/
private void createSynthesizedExternVar(String varName) {
Node nameNode = IR.name(varName);
// Mark the variable as constant if it matches the coding convention
// for constant vars.
// NOTE(nicksantos): honestly, I'm not sure how much this matters.
// AFAIK, all people who use the CONST coding convention also
// compile with undeclaredVars as errors. We have some test
// cases for this configuration though, and it makes them happier.
if (compiler.getCodingConvention().isConstant(varName))
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> {
nameNode.putBooleanProp(Node.IS_CONSTANT_NAME, true);
}
getSynthesizedExternsRoot().addChildToBack(
IR.var(nameNode));
varsToDeclareInExterns.remove(varName);
compiler.reportCodeChange();
}
/**
* A check for name references in the externs inputs. These used to prevent
* a variable from getting renamed, but no longer have any effect.
*/
private class NameRefInExternsCheck extends AbstractPostOrderCallback {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.isName()) {
switch (parent.getType()) {
case Token.VAR:
case Token.FUNCTION:
case Token.PARAM_LIST:
// These are okay.
break;
case Token.GETPROP:
if (n == parent.getFirstChild()) {
Scope scope = t.getScope();
Scope.Var var = scope.getVar(n.getString());
if (var == null) {
t.report(n, UNDEFINED_EXTERN_VAR_ERROR, n.getString());
varsToDeclareInExterns.add(n.getString());
}
}
break;
default:
t.report(n, NAME_REFERENCE_IN_EXTERNS_ERROR, n.getString());
Scope scope = t.getScope();
Scope.Var var = scope.getVar(n.getString());
if (var == null) {
varsToDeclareInExterns.add(n.getString());
}
break;
}
}
}
}
/** Lazily create a "new" externs input for undeclared variables. */
private CompilerInput getSynthesizedExternsInput() {
return compiler.getSynthesizedExternsInput();
}
/** Lazily create a "new" externs root for undeclared variables. */
private Node getSynthesizedExternsRoot() {
if (synthesizedExternsRoot == null) {
CompilerInput synthesizedExterns = getSynthesizedExternsInput();
synthesizedExternsRoot = synthesizedExterns.getAstRoot(compiler);
}
return synthesizedExternsRoot;
}
}
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> Deque<BasicBlock> blockStack = new ArrayDeque<BasicBlock>();
/**
* Source of behavior at various points in the traversal.
*/
private final Behavior behavior;
/**
* JavaScript compiler to use in traversing.
*/
private final AbstractCompiler compiler;
/**
* Only collect references for filtered variables.
*/
private final Predicate<Var> varFilter;
/**
* Constructor initializes block stack.
*/
ReferenceCollectingCallback(AbstractCompiler compiler, Behavior behavior) {
this(compiler, behavior, Predicates.<Var>alwaysTrue());
}
/**
* Constructor only collects references that match the given variable.
*
* The test for Var equality uses reference equality, so it's necessary to
* inject a scope when you traverse.
*/
ReferenceCollectingCallback(AbstractCompiler compiler, Behavior behavior,
Predicate<Var> varFilter) {
this.compiler = compiler;
this.behavior = behavior;
this.varFilter = varFilter;
}
/**
* Convenience method for running this pass over a tree with this
* class as a callback.
*/
@Override
public void process(Node externs, Node root) {
NodeTraversal.traverseRoots(
compiler, Lists.newArrayList(externs, root), this);
}
/**
* Same as process but only runs on a part of AST associated to one script.
*/
@Override
public void hotSwapScript(Node scriptRoot, Node originalRoot) {
NodeTraversal.traverse(compiler, scriptRoot, this);
}
/**
* Gets the variables that were referenced in this callback.
*/
@Override
public Iterable<Var> getAllSymbols() {
return referenceMap.keySet();
}
@Override
public Scope getScope(Var var) {
return var.scope;
}
/**
* Gets the reference collection for the given variable.
*/
@Override
public ReferenceCollection getReferences(Var v) {
return referenceMap.get(v);
}
/**
* For each node, update the block stack and reference collection
* as appropriate.
*/
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.isName()) {
Var v;
if (n.getString().equals("arguments")) {
v = t.getScope().getArgumentsVar();
} else {
v = t.getScope().getVar(n.getString());
}
if (v != null && varFilter.apply(v)) {
addReference(t, v, new Reference(n, t, blockStack.peek()));
}
}
if (isBlockBoundary(n, parent)) {
blockStack.pop();
}
}
/**
* Updates block stack and invokes any additional behavior.
*/
@Override
public void enterScope(NodeTraversal t) {
Node n = t.getScope().getRootNode();
BasicBlock parent = blockStack.isEmpty() ? null : blockStack.peek();
block
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>Stack.push(new BasicBlock(parent, n));
}
/**
* Updates block stack and invokes any additional behavior.
*/
@Override
public void exitScope(NodeTraversal t) {
blockStack.pop();
if (t.getScope().isGlobal()) {
// Update global scope reference lists when we are done with it.
compiler.updateGlobalVarReferences(referenceMap, t.getScopeRoot());
behavior.afterExitScope(t, compiler.getGlobalVarReferences());
} else {
behavior.afterExitScope(t, new ReferenceMapWrapper(referenceMap));
}
}
/**
* Updates block stack.
*/
@Override
public boolean shouldTraverse(NodeTraversal nodeTraversal, Node n,
Node parent) {
// If node is a new basic block, put on basic block stack
if (isBlockBoundary(n, parent)) {
blockStack.push(new BasicBlock(blockStack.peek(), n));
}
return true;
}
/**
* @return true if this node marks the start of a new basic block
*/
private static boolean isBlockBoundary(Node n, Node parent) {
if (parent != null) {
switch (parent.getType()) {
case Token.DO:
case Token.FOR:
case Token.TRY:
case Token.WHILE:
case Token.WITH:
// NOTE: TRY has up to 3 child blocks:
// TRY
// BLOCK
// BLOCK
// CATCH
// BLOCK
// Note that there is an explicit CATCH token but no explicit
// FINALLY token. For simplicity, we consider each BLOCK
// a separate basic BLOCK.
return true;
case Token.AND:
case Token.HOOK:
case Token.IF:
case Token.OR:
// The first child of a conditional is not a boundary,
// but all the rest of the children are.
return n != parent.getFirstChild();
}
}
return n.isCase();
}
private void addReference(NodeTraversal t, Var v, Reference reference) {
// Create collection if none already
ReferenceCollection referenceInfo = referenceMap.get(v);
if (referenceInfo == null) {
referenceInfo = new ReferenceCollection();
referenceMap.put(v, referenceInfo);
}
// Add this particular reference
referenceInfo.add(reference, t, v);
}
interface ReferenceMap {
ReferenceCollection getReferences(Var var);
}
private static class ReferenceMapWrapper implements ReferenceMap {
private final Map<Var, ReferenceCollection> referenceMap;
public ReferenceMapWrapper(Map<Var, ReferenceCollection> referenceMap) {
this.referenceMap = referenceMap;
}
@Override
public ReferenceCollection getReferences(Var var) {
return referenceMap.get(var);
}
}
/**
* Way for callers to add specific behavior during traversal that
* utilizes the built-up reference information.
*/
interface
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> Behavior {
/**
* Called after we finish with a scope.
*/
void afterExitScope(NodeTraversal t, ReferenceMap referenceMap);
}
static Behavior DO_NOTHING_BEHAVIOR = new Behavior() {
@Override
public void afterExitScope(NodeTraversal t, ReferenceMap referenceMap) {}
};
/**
* A collection of references. Can be subclassed to apply checks or
* store additional state when adding.
*/
static class ReferenceCollection implements Iterable<Reference> {
List<Reference> references = Lists.newArrayList();
@Override
public Iterator<Reference> iterator() {
return references.iterator();
}
void add(Reference reference, NodeTraversal t, Var v) {
references.add(reference);
}
/**
* Determines if the variable for this reference collection is
* "well-defined." A variable is well-defined if we can prove at
* compile-time that it's assigned a value before it's used.
*
* Notice that if this function returns false, this doesn't imply that the
* variable is used before it's assigned. It just means that we don't
* have enough information to make a definitive judgment.
*/
protected boolean isWellDefined() {
int size = references.size();
if (size == 0) {
return false;
}
// If this is a declaration that does not instantiate the variable,
// it's not well-defined.
Reference init = getInitializingReference();
if (init == null) {
return false;
}
Preconditions.checkState(references.get(0).isDeclaration());
BasicBlock initBlock = init.getBasicBlock();
for (int i = 1; i < size; i++) {
if (!initBlock.provablyExecutesBefore(
references.get(i).getBasicBlock())) {
return false;
}
}
return true;
}
/**
* Whether the variable is escaped into an inner scope.
*/
boolean isEscaped() {
Scope scope = null;
for (Reference ref : references) {
if (scope == null) {
scope = ref.scope;
} else if (scope != ref.scope) {
return true;
}
}
return false;
}
/**
* @param index The index into the references array to look for an
* assigning declaration.
*
* This is either the declaration if a value is assigned (such as
* "var a = 2", "function a()...", "... catch (a)...").
*/
private boolean isInitializingDeclarationAt(int index) {
Reference maybeInit = references.get(index);
if (maybeInit.isInitializingDeclaration()) {
// This is a declaration that represents the initial value.
// Specifically, var declarations without assignments such as "var a;"
// are not.
return true;
}
return false;
}
/**
* @param index The index into the references array to look
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>.get(i);
if (ref.isLvalue() || ref.isInitializingDeclaration()) {
return false;
}
}
return true;
}
boolean firstReferenceIsAssigningDeclaration() {
int size = references.size();
if (size > 0 && references.get(0).isInitializingDeclaration()) {
return true;
}
return false;
}
}
/**
* Represents a single declaration or reference to a variable.
*/
static final class Reference implements StaticReference<JSType> {
private static final Set<Integer> DECLARATION_PARENTS =
ImmutableSet.of(Token.VAR, Token.FUNCTION, Token.CATCH);
private final Node nameNode;
private final BasicBlock basicBlock;
private final Scope scope;
private final InputId inputId;
private final StaticSourceFile sourceFile;
Reference(Node nameNode, NodeTraversal t,
BasicBlock basicBlock) {
this(nameNode, basicBlock, t.getScope(), t.getInput().getInputId());
}
// Bleeding functions are weird, because the declaration does
// not appear inside their scope. So they need their own constructor.
static Reference newBleedingFunction(NodeTraversal t,
BasicBlock basicBlock, Node func) {
return new Reference(func.getFirstChild(),
basicBlock, t.getScope(), t.getInput().getInputId());
}
/**
* Creates a variable reference in a given script file name, used in tests.
*
* @return The created reference.
*/
@VisibleForTesting
static Reference createRefForTest(CompilerInput input) {
return new Reference(new Node(Token.NAME), null, null,
input.getInputId());
}
private Reference(Node nameNode,
BasicBlock basicBlock, Scope scope, InputId inputId) {
this.nameNode = nameNode;
this.basicBlock = basicBlock;
this.scope = scope;
this.inputId = inputId;
this.sourceFile = nameNode.getStaticSourceFile();
}
/**
* Makes a copy of the current reference using a new Scope instance.
*/
Reference cloneWithNewScope(Scope newScope) {
return new Reference(nameNode, basicBlock, newScope, inputId);
}
@Override
public Var getSymbol() {
return scope.getVar(nameNode.getString());
}
@Override
public Node getNode() {
return nameNode;
}
public InputId getInputId() {
return inputId;
}
@Override
public StaticSourceFile getSourceFile() {
return sourceFile;
}
boolean isDeclaration() {
Node parent = getParent();
Node grandparent = parent.getParent();
return DECLARATION_PARENTS.contains(parent.getType()) ||
parent.isParamList() &&
grandparent.isFunction();
}
boolean isVarDeclaration() {
return getParent().isVar();
}
boolean isHoistedFunction() {
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>
return NodeUtil.isHoistedFunctionDeclaration(getParent());
}
/**
* Determines whether the variable is initialized at the declaration.
*/
boolean isInitializingDeclaration() {
// VAR is the only type of variable declaration that may not initialize
// its variable. Catch blocks, named functions, and parameters all do.
return isDeclaration() &&
!getParent().isVar() ||
nameNode.getFirstChild() != null;
}
/**
* @return For an assignment, variable declaration, or function declaration
* return the assigned value, otherwise null.
*/
Node getAssignedValue() {
Node parent = getParent();
return (parent.isFunction())
? parent : NodeUtil.getAssignedValue(nameNode);
}
BasicBlock getBasicBlock() {
return basicBlock;
}
Node getParent() {
return getNode().getParent();
}
Node getGrandparent() {
Node parent = getParent();
return parent == null ? null : parent.getParent();
}
private static boolean isLhsOfForInExpression(Node n) {
Node parent = n.getParent();
if (parent.isVar()) {
return isLhsOfForInExpression(parent);
}
return NodeUtil.isForIn(parent) && parent.getFirstChild() == n;
}
boolean isSimpleAssignmentToName() {
Node parent = getParent();
return parent.isAssign()
&& parent.getFirstChild() == nameNode;
}
boolean isLvalue() {
Node parent = getParent();
int parentType = parent.getType();
return (parentType == Token.VAR && nameNode.getFirstChild() != null)
|| parentType == Token.INC
|| parentType == Token.DEC
|| (NodeUtil.isAssignmentOp(parent)
&& parent.getFirstChild() == nameNode)
|| isLhsOfForInExpression(nameNode);
}
Scope getScope() {
return scope;
}
}
/**
* Represents a section of code that is uninterrupted by control structures
* (conditional or iterative logic).
*/
static final class BasicBlock {
private final BasicBlock parent;
/**
* Determines whether the block may not be part of the normal control flow,
* but instead "hoisted" to the top of the scope.
*/
private final boolean isHoisted;
/**
* Whether this block denotes a function scope.
*/
private final boolean isFunction;
/**
* Whether this block denotes a loop.
*/
private final boolean isLoop;
/**
* Creates a new block.
* @param parent The containing block.
* @param root The root node of the block.
*/
BasicBlock(BasicBlock parent, Node root) {
this.parent = parent;
// only named functions may be hoisted.
this.isHoisted = NodeUtil.isHoistedFunctionDeclaration(root);
this.isFunction = root.isFunction();
if (root.getParent() != null) {
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> int pType = root.getParent().getType();
this.isLoop = pType == Token.DO ||
pType == Token.WHILE ||
pType == Token.FOR;
} else {
this.isLoop = false;
}
}
BasicBlock getParent() {
return parent;
}
/**
* Determines whether this block is equivalent to the very first block that
* is created when reference collection traversal enters global scope. Note
* that when traversing a single script in a hot-swap fashion a new instance
* of {@code BasicBlock} is created.
*
* @return true if this is global scope block.
*/
boolean isGlobalScopeBlock() {
return getParent() == null;
}
/**
* Determines whether this block is guaranteed to begin executing before
* the given block does.
*/
boolean provablyExecutesBefore(BasicBlock thatBlock) {
// If thatBlock is a descendant of this block, and there are no hoisted
// blocks between them, then this block must start before thatBlock.
BasicBlock currentBlock;
for (currentBlock = thatBlock;
currentBlock != null && currentBlock != this;
currentBlock = currentBlock.getParent()) {
if (currentBlock.isHoisted) {
return false;
}
}
if (currentBlock == this) {
return true;
}
if (isGlobalScopeBlock() && thatBlock.isGlobalScopeBlock()) {
return true;
}
return false;
}
}
}
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>UNKNOWN_EXPR_TYPE",
"could not determine the type of this expression");
static final DiagnosticType UNRESOLVED_TYPE =
DiagnosticType.warning("JSC_UNRESOLVED_TYPE",
"could not resolve the name {0} to a type");
static final DiagnosticType WRONG_ARGUMENT_COUNT =
DiagnosticType.warning(
"JSC_WRONG_ARGUMENT_COUNT",
"Function {0}: called with {1} argument(s). " +
"Function requires at least {2} argument(s){3}.");
static final DiagnosticType ILLEGAL_IMPLICIT_CAST =
DiagnosticType.warning(
"JSC_ILLEGAL_IMPLICIT_CAST",
"Illegal annotation on {0}. @implicitCast may only be used in " +
"externs.");
static final DiagnosticType INCOMPATIBLE_EXTENDED_PROPERTY_TYPE =
DiagnosticType.warning(
"JSC_INCOMPATIBLE_EXTENDED_PROPERTY_TYPE",
"Interface {0} has a property {1} with incompatible types in " +
"its super interfaces {2} and {3}");
static final DiagnosticType EXPECTED_THIS_TYPE =
DiagnosticType.warning(
"JSC_EXPECTED_THIS_TYPE",
"\"{0}\" must be called with a \"this\" type");
static final DiagnosticGroup ALL_DIAGNOSTICS = new DiagnosticGroup(
DETERMINISTIC_TEST,
DETERMINISTIC_TEST_NO_RESULT,
INEXISTENT_ENUM_ELEMENT,
INEXISTENT_PROPERTY,
NOT_A_CONSTRUCTOR,
BIT_OPERATION,
NOT_CALLABLE,
CONSTRUCTOR_NOT_CALLABLE,
FUNCTION_MASKS_VARIABLE,
MULTIPLE_VAR_DEF,
ENUM_DUP,
ENUM_NOT_CONSTANT,
INVALID_INTERFACE_MEMBER_DECLARATION,
INTERFACE_FUNCTION_NOT_EMPTY,
CONFLICTING_EXTENDED_TYPE,
CONFLICTING_IMPLEMENTED_TYPE,
BAD_IMPLEMENTED_TYPE,
HIDDEN_SUPERCLASS_PROPERTY,
HIDDEN_INTERFACE_PROPERTY,
HIDDEN_SUPERCLASS_PROPERTY_MISMATCH,
UNKNOWN_OVERRIDE,
INTERFACE_METHOD_OVERRIDE,
UNKNOWN_EXPR_TYPE,
UNRESOLVED_TYPE,
WRONG_ARGUMENT_COUNT,
ILLEGAL_IMPLICIT_CAST,
INCOMPATIBLE_EXTENDED_PROPERTY_TYPE,
EXPECTED_THIS_TYPE,
RhinoErrorReporter.TYPE_PARSE_ERROR,
TypedScopeCreator.UNKNOWN_LENDS,
TypedScopeCreator.LENDS_ON_NON_OBJECT,
TypedScopeCreator.CTOR_INITIALIZER,
TypedScopeCreator.IFACE_INITIALIZER,
FunctionTypeBuilder.THIS_TYPE_NON_OBJECT);
private final AbstractCompiler compiler;
private final TypeValidator validator;
private final ReverseAbstractInterpreter reverseInterpreter;
private final JSTypeRegistry typeRegistry;
private Scope topScope;
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>
private ScopeCreator scopeCreator;
private final CheckLevel reportMissingOverride;
private final CheckLevel reportUnknownTypes;
// This may be expensive, so don't emit these warnings if they're
// explicitly turned off.
private boolean reportMissingProperties = true;
private InferJSDocInfo inferJSDocInfo = null;
// These fields are used to calculate the percentage of expressions typed.
private int typedCount = 0;
private int nullCount = 0;
private int unknownCount = 0;
private boolean inExterns;
// A state boolean to see we are currently in @notypecheck section of the
// code.
private int noTypeCheckSection = 0;
public TypeCheck(AbstractCompiler compiler,
ReverseAbstractInterpreter reverseInterpreter,
JSTypeRegistry typeRegistry,
Scope topScope,
ScopeCreator scopeCreator,
CheckLevel reportMissingOverride,
CheckLevel reportUnknownTypes) {
this.compiler = compiler;
this.validator = compiler.getTypeValidator();
this.reverseInterpreter = reverseInterpreter;
this.typeRegistry = typeRegistry;
this.topScope = topScope;
this.scopeCreator = scopeCreator;
this.reportMissingOverride = reportMissingOverride;
this.reportUnknownTypes = reportUnknownTypes;
this.inferJSDocInfo = new InferJSDocInfo(compiler);
}
public TypeCheck(AbstractCompiler compiler,
ReverseAbstractInterpreter reverseInterpreter,
JSTypeRegistry typeRegistry,
CheckLevel reportMissingOverride,
CheckLevel reportUnknownTypes) {
this(compiler, reverseInterpreter, typeRegistry, null, null,
reportMissingOverride, reportUnknownTypes);
}
TypeCheck(AbstractCompiler compiler,
ReverseAbstractInterpreter reverseInterpreter,
JSTypeRegistry typeRegistry) {
this(compiler, reverseInterpreter, typeRegistry, null, null,
CheckLevel.WARNING, CheckLevel.OFF);
}
/** Turn on the missing property check. Returns this for easy chaining. */
TypeCheck reportMissingProperties(boolean report) {
reportMissingProperties = report;
return this;
}
/**
* Main entry point for this phase of processing. This follows the pattern for
* JSCompiler phases.
*
* @param externsRoot The root of the externs parse tree.
* @param jsRoot The root of the input parse tree to be checked.
*/
@Override
public void process(Node externsRoot, Node jsRoot) {
Preconditions.checkNotNull(scopeCreator);
Preconditions.checkNotNull(topScope);
Node externsAndJs = jsRoot.getParent();
Preconditions.checkState(externsAndJs != null);
Preconditions.checkState(
externsRoot == null || externsAndJs.hasChild(externsRoot));
if (externsRoot != null) {
check(externsRoot, true);
}
check(jsRoot, false);
}
/** Main entry point of this phase for testing code. */
public Scope processForTesting(Node externs
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>Root, Node jsRoot) {
Preconditions.checkState(scopeCreator == null);
Preconditions.checkState(topScope == null);
Preconditions.checkState(jsRoot.getParent() != null);
Node externsAndJsRoot = jsRoot.getParent();
scopeCreator = new MemoizedScopeCreator(new TypedScopeCreator(compiler));
topScope = scopeCreator.createScope(externsAndJsRoot, null);
TypeInferencePass inference = new TypeInferencePass(compiler,
reverseInterpreter, topScope, scopeCreator);
inference.process(externsRoot, jsRoot);
process(externsRoot, jsRoot);
return topScope;
}
public void check(Node node, boolean externs) {
Preconditions.checkNotNull(node);
NodeTraversal t = new NodeTraversal(compiler, this, scopeCreator);
inExterns = externs;
t.traverseWithScope(node, topScope);
if (externs) {
inferJSDocInfo.process(node, null);
} else {
inferJSDocInfo.process(null, node);
}
}
private void checkNoTypeCheckSection(Node n, boolean enterSection) {
switch (n.getType()) {
case Token.SCRIPT:
case Token.BLOCK:
case Token.VAR:
case Token.FUNCTION:
case Token.ASSIGN:
JSDocInfo info = n.getJSDocInfo();
if (info != null && info.isNoTypeCheck()) {
if (enterSection) {
noTypeCheckSection++;
} else {
noTypeCheckSection--;
}
}
validator.setShouldReport(noTypeCheckSection == 0);
break;
}
}
private void report(NodeTraversal t, Node n, DiagnosticType diagnosticType,
String... arguments) {
if (noTypeCheckSection == 0) {
t.report(n, diagnosticType, arguments);
}
}
@Override
public boolean shouldTraverse(
NodeTraversal t, Node n, Node parent) {
checkNoTypeCheckSection(n, true);
switch (n.getType()) {
case Token.FUNCTION:
// normal type checking
final Scope outerScope = t.getScope();
final String functionPrivateName = n.getFirstChild().getString();
if (functionPrivateName != null && functionPrivateName.length() > 0 &&
outerScope.isDeclared(functionPrivateName, false) &&
// Ideally, we would want to check whether the type in the scope
// differs from the type being defined, but then the extern
// redeclarations of built-in types generates spurious warnings.
!(outerScope.getVar(
functionPrivateName).getType() instanceof FunctionType)) {
report(t, n, FUNCTION_MASKS_VARIABLE, functionPrivateName);
}
// TODO(user): Only traverse the function's body. The function's
// name and arguments are traversed by the scope creator, and ideally
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>
// should not be traversed by the type checker.
break;
}
return true;
}
/**
* This is the meat of the type checking. It is basically one big switch,
* with each case representing one type of parse tree node. The individual
* cases are usually pretty straightforward.
*
* @param t The node traversal object that supplies context, such as the
* scope chain to use in name lookups as well as error reporting.
* @param n The node being visited.
* @param parent The parent of the node n.
*/
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
JSType childType;
JSType leftType, rightType;
Node left, right;
// To be explicitly set to false if the node is not typeable.
boolean typeable = true;
switch (n.getType()) {
case Token.NAME:
typeable = visitName(t, n, parent);
break;
case Token.PARAM_LIST:
// If this is under a FUNCTION node, it is a parameter list and can be
// ignored here.
if (!parent.isFunction()) {
ensureTyped(t, n, getJSType(n.getFirstChild()));
} else {
typeable = false;
}
break;
case Token.COMMA:
ensureTyped(t, n, getJSType(n.getLastChild()));
break;
case Token.TRUE:
case Token.FALSE:
ensureTyped(t, n, BOOLEAN_TYPE);
break;
case Token.THIS:
ensureTyped(t, n, t.getScope().getTypeOfThis());
break;
case Token.NULL:
ensureTyped(t, n, NULL_TYPE);
break;
case Token.NUMBER:
ensureTyped(t, n, NUMBER_TYPE);
break;
case Token.STRING:
ensureTyped(t, n, STRING_TYPE);
break;
case Token.STRING_KEY:
typeable = false;
break;
case Token.GETTER_DEF:
case Token.SETTER_DEF:
// Object literal keys are handled with OBJECTLIT
break;
case Token.ARRAYLIT:
ensureTyped(t, n, ARRAY_TYPE);
break;
case Token.REGEXP:
ensureTyped(t, n, REGEXP_TYPE);
break;
case Token.GETPROP:
visitGetProp(t, n, parent);
typeable = !(parent.isAssign() &&
parent.getFirstChild() == n);
break;
case Token.GETELEM:
visitGetElem(t, n);
// The type of GETELEM is always unknown, so no point counting that.
// If that unknown leaks elsewhere (say by an assignment to another
// variable), then it will be counted.
typeable = false;
break;
case Token.VAR:
visitVar(t, n);
typeable
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> = false;
break;
case Token.NEW:
visitNew(t, n);
typeable = true;
break;
case Token.CALL:
visitCall(t, n);
typeable = !parent.isExprResult();
break;
case Token.RETURN:
visitReturn(t, n);
typeable = false;
break;
case Token.DEC:
case Token.INC:
left = n.getFirstChild();
validator.expectNumber(
t, left, getJSType(left), "increment/decrement");
ensureTyped(t, n, NUMBER_TYPE);
break;
case Token.NOT:
ensureTyped(t, n, BOOLEAN_TYPE);
break;
case Token.VOID:
ensureTyped(t, n, VOID_TYPE);
break;
case Token.TYPEOF:
ensureTyped(t, n, STRING_TYPE);
break;
case Token.BITNOT:
childType = getJSType(n.getFirstChild());
if (!childType.matchesInt32Context()) {
report(t, n, BIT_OPERATION, NodeUtil.opToStr(n.getType()),
childType.toString());
}
ensureTyped(t, n, NUMBER_TYPE);
break;
case Token.POS:
case Token.NEG:
left = n.getFirstChild();
validator.expectNumber(t, left, getJSType(left), "sign operator");
ensureTyped(t, n, NUMBER_TYPE);
break;
case Token.EQ:
case Token.NE:
case Token.SHEQ:
case Token.SHNE: {
leftType = getJSType(n.getFirstChild());
rightType = getJSType(n.getLastChild());
// We do not want to warn about explicit comparisons to VOID. People
// often do this if they think their type annotations screwed up.
//
// We do want to warn about cases where people compare things like
// (Array|null) == (Function|null)
// because it probably means they screwed up.
//
// This heuristic here is not perfect, but should catch cases we
// care about without too many false negatives.
JSType leftTypeRestricted = leftType.restrictByNotNullOrUndefined();
JSType rightTypeRestricted = rightType.restrictByNotNullOrUndefined();
TernaryValue result = TernaryValue.UNKNOWN;
if (n.getType() == Token.EQ || n.getType() == Token.NE) {
result = leftTypeRestricted.testForEquality(
rightTypeRestricted);
if (n.isNE()) {
result = result.not();
}
} else {
// SHEQ or SHNE
if (!leftTypeRestricted.canTestForShallowEqualityWith(
rightTypeRestricted)) {
result = n.getType() == Token.SHEQ ?
TernaryValue.FALSE : TernaryValue.TRUE;
}
}
if (result
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>.ASSIGN_SUB:
case Token.ASSIGN_ADD:
case Token.ASSIGN_MUL:
case Token.LSH:
case Token.RSH:
case Token.URSH:
case Token.DIV:
case Token.MOD:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
case Token.SUB:
case Token.ADD:
case Token.MUL:
visitBinaryOperator(n.getType(), t, n);
break;
case Token.DELPROP:
ensureTyped(t, n, BOOLEAN_TYPE);
break;
case Token.CASE:
JSType switchType = getJSType(parent.getFirstChild());
JSType caseType = getJSType(n.getFirstChild());
validator.expectSwitchMatchesCase(t, n, switchType, caseType);
typeable = false;
break;
case Token.WITH: {
Node child = n.getFirstChild();
childType = getJSType(child);
validator.expectObject(
t, child, childType, "with requires an object");
typeable = false;
break;
}
case Token.FUNCTION:
visitFunction(t, n);
break;
// These nodes have no interesting type behavior.
case Token.LABEL:
case Token.LABEL_NAME:
case Token.SWITCH:
case Token.BREAK:
case Token.CATCH:
case Token.TRY:
case Token.SCRIPT:
case Token.EXPR_RESULT:
case Token.BLOCK:
case Token.EMPTY:
case Token.DEFAULT_CASE:
case Token.CONTINUE:
case Token.DEBUGGER:
case Token.THROW:
typeable = false;
break;
// These nodes require data flow analysis.
case Token.DO:
case Token.FOR:
case Token.IF:
case Token.WHILE:
typeable = false;
break;
// These nodes are typed during the type inference.
case Token.AND:
case Token.HOOK:
case Token.OBJECTLIT:
case Token.OR:
if (n.getJSType() != null) { // If we didn't run type inference.
ensureTyped(t, n);
} else {
// If this is an enum, then give that type to the objectlit as well.
if ((n.isObjectLit())
&& (parent.getJSType() instanceof EnumType)) {
ensureTyped(t, n, parent.getJSType());
} else {
ensureTyped(t, n);
}
}
if (n.isObjectLit()) {
for (Node key : n.children()) {
visitObjLitKey(t, key, n);
}
}
break;
default:
report(t, n, UNEXPECTED_TOKEN, Token.name(n.getType()));
ensureTyped(t, n);
break;
}
// Don't count externs since the user's code may not even
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> use that part.
typeable = typeable && !inExterns;
if (typeable) {
doPercentTypedAccounting(t, n);
}
checkNoTypeCheckSection(n, false);
}
/**
* Counts the given node in the typed statistics.
* @param n a node that should be typed
*/
private void doPercentTypedAccounting(NodeTraversal t, Node n) {
JSType type = n.getJSType();
if (type == null) {
nullCount++;
} else if (type.isUnknownType()) {
if (reportUnknownTypes.isOn()) {
compiler.report(
t.makeError(n, reportUnknownTypes, UNKNOWN_EXPR_TYPE));
}
unknownCount++;
} else {
typedCount++;
}
}
/**
* Visits an assignment <code>lvalue = rvalue</code>. If the
* <code>lvalue</code> is a prototype modification, we change the schema
* of the object type it is referring to.
* @param t the traversal
* @param assign the assign node
* (<code>assign.isAssign()</code> is an implicit invariant)
*/
private void visitAssign(NodeTraversal t, Node assign) {
JSDocInfo info = assign.getJSDocInfo();
Node lvalue = assign.getFirstChild();
Node rvalue = assign.getLastChild();
// Check property sets to 'object.property' when 'object' is known.
if (lvalue.isGetProp()) {
Node object = lvalue.getFirstChild();
JSType objectJsType = getJSType(object);
String property = lvalue.getLastChild().getString();
// the first name in this getprop refers to an interface
// we perform checks in addition to the ones below
if (object.isGetProp()) {
JSType jsType = getJSType(object.getFirstChild());
if (jsType.isInterface() &&
object.getLastChild().getString().equals("prototype")) {
visitInterfaceGetprop(t, assign, object, property, lvalue, rvalue);
}
}
checkEnumAlias(t, info, rvalue);
// Prototype assignments are special, because they actually affect
// the definition of a class. These are mostly validated
// during TypedScopeCreator, and we only look for the "dumb" cases here.
// object.prototype = ...;
if (property.equals("prototype")) {
if (objectJsType != null && objectJsType.isFunctionType()) {
FunctionType functionType = objectJsType.toMaybeFunctionType();
if (functionType.isConstructor()) {
JSType rvalueType = rvalue.getJSType();
validator.expectObject(t, rvalue, rvalueType,
OVERRIDING_PROTOTYPE_WITH_NON_OBJECT);
return;
}
}
}
// The generic checks for 'object.property' when '
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>object' is known,
// and 'property' is declared on it.
// object.property = ...;
ObjectType type = ObjectType.cast(
objectJsType.restrictByNotNullOrUndefined());
if (type != null) {
if (type.hasProperty(property) &&
!type.isPropertyTypeInferred(property) &&
!propertyIsImplicitCast(type, property)) {
JSType expectedType = type.getPropertyType(property);
if (!expectedType.isUnknownType()) {
validator.expectCanAssignToPropertyOf(
t, assign, getJSType(rvalue),
expectedType, object, property);
checkPropertyInheritanceOnGetpropAssign(
t, assign, object, property, info, expectedType);
return;
}
}
}
// If we couldn't get the property type with normal object property
// lookups, then check inheritance anyway with the unknown type.
checkPropertyInheritanceOnGetpropAssign(
t, assign, object, property, info, getNativeType(UNKNOWN_TYPE));
}
// Check qualified name sets to 'object' and 'object.property'.
// This can sometimes handle cases when the type of 'object' is not known.
// e.g.,
// var obj = createUnknownType();
// /** @type {number} */ obj.foo = true;
JSType leftType = getJSType(lvalue);
if (lvalue.isQualifiedName()) {
// variable with inferred type case
JSType rvalueType = getJSType(assign.getLastChild());
Var var = t.getScope().getVar(lvalue.getQualifiedName());
if (var != null) {
if (var.isTypeInferred()) {
return;
}
if (NodeUtil.getRootOfQualifiedName(lvalue).isThis() &&
t.getScope() != var.getScope()) {
// Don't look at "this.foo" variables from other scopes.
return;
}
if (var.getType() != null) {
leftType = var.getType();
}
}
}
// Fall through case for arbitrary LHS and arbitrary RHS.
Node rightChild = assign.getLastChild();
JSType rightType = getJSType(rightChild);
if (validator.expectCanAssignTo(
t, assign, rightType, leftType, "assignment")) {
ensureTyped(t, assign, rightType);
} else {
ensureTyped(t, assign);
}
}
private void checkPropertyInheritanceOnGetpropAssign(
NodeTraversal t, Node assign, Node object, String property,
JSDocInfo info, JSType propertyType) {
// Inheritance checks for prototype properties.
//
// TODO(nicksantos): This isn't the right place to do this check. We
// really want to do this when we're looking at the constructor.
// We'd find all its properties and make sure they followed inheritance
// rules, like we currently do for
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>.cast(
objlitType.restrictByNotNullOrUndefined());
if (type != null) {
String property = NodeUtil.getObjectLitKeyName(key);
if (type.hasProperty(property) &&
!type.isPropertyTypeInferred(property) &&
!propertyIsImplicitCast(type, property)) {
validator.expectCanAssignToPropertyOf(
t, key, keyType,
type.getPropertyType(property), owner, property);
}
return;
}
}
/**
* Returns true if any type in the chain has an implicitCast annotation for
* the given property.
*/
private boolean propertyIsImplicitCast(ObjectType type, String prop) {
for (; type != null; type = type.getImplicitPrototype()) {
JSDocInfo docInfo = type.getOwnPropertyJSDocInfo(prop);
if (docInfo != null && docInfo.isImplicitCast()) {
return true;
}
}
return false;
}
/**
* Given a constructor type and a property name, check that the property has
* the JSDoc annotation @override iff the property is declared on a
* superclass. Several checks regarding inheritance correctness are also
* performed.
*/
private void checkDeclaredPropertyInheritance(
NodeTraversal t, Node n, FunctionType ctorType, String propertyName,
JSDocInfo info, JSType propertyType) {
// If the supertype doesn't resolve correctly, we've warned about this
// already.
if (hasUnknownOrEmptySupertype(ctorType)) {
return;
}
FunctionType superClass = ctorType.getSuperClassConstructor();
boolean superClassHasProperty = superClass != null &&
superClass.getInstanceType().hasProperty(propertyName);
boolean superClassHasDeclaredProperty = superClass != null &&
superClass.getInstanceType().isPropertyTypeDeclared(propertyName);
// For interface
boolean superInterfaceHasProperty = false;
boolean superInterfaceHasDeclaredProperty = false;
if (ctorType.isInterface()) {
for (ObjectType interfaceType : ctorType.getExtendedInterfaces()) {
superInterfaceHasProperty =
superInterfaceHasProperty ||
interfaceType.hasProperty(propertyName);
superInterfaceHasDeclaredProperty =
superInterfaceHasDeclaredProperty ||
interfaceType.isPropertyTypeDeclared(propertyName);
}
}
boolean declaredOverride = info != null && info.isOverride();
boolean foundInterfaceProperty = false;
if (ctorType.isConstructor()) {
for (JSType implementedInterface :
ctorType.getAllImplementedInterfaces()) {
if (implementedInterface.isUnknownType() ||
implementedInterface.isEmptyType()) {
continue;
}
FunctionType interfaceType =
implementedInterface.toObjectType().getConstructor();
Preconditions.checkNotNull(interfaceType);
boolean interfaceHasProperty =
interfaceType.getPrototype().hasProperty(propertyName);
foundInterfaceProperty = foundInterfaceProperty ||
interfaceHasProperty;
if (reportMissingOverride.isOn()
&& !declaredOverride
&& interfaceHasProperty) {
// @override
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> inheritance cycles on its own
// and break the cycle.
while (true) {
ObjectType maybeSuperInstanceType =
ctor.getPrototype().getImplicitPrototype();
if (maybeSuperInstanceType == null) {
return false;
}
if (maybeSuperInstanceType.isUnknownType() ||
maybeSuperInstanceType.isEmptyType()) {
return true;
}
ctor = maybeSuperInstanceType.getConstructor();
if (ctor == null) {
return false;
}
Preconditions.checkState(ctor.isConstructor() || ctor.isInterface());
}
}
/**
* Visits an ASSIGN node for cases such as
* <pre>
* interface.property2.property = ...;
* </pre>
*/
private void visitInterfaceGetprop(NodeTraversal t, Node assign, Node object,
String property, Node lvalue, Node rvalue) {
JSType rvalueType = getJSType(rvalue);
// Only 2 values are allowed for methods:
// goog.abstractMethod
// function () {};
// or for properties, no assignment such as:
// InterfaceFoo.prototype.foobar;
String abstractMethodName =
compiler.getCodingConvention().getAbstractMethodName();
if (!rvalueType.isFunctionType()) {
// This is bad i18n style but we don't localize our compiler errors.
String abstractMethodMessage = (abstractMethodName != null)
? ", or " + abstractMethodName
: "";
compiler.report(
t.makeError(object, INVALID_INTERFACE_MEMBER_DECLARATION,
abstractMethodMessage));
}
if (assign.getLastChild().isFunction()
&& !NodeUtil.isEmptyBlock(assign.getLastChild().getLastChild())) {
compiler.report(
t.makeError(object, INTERFACE_FUNCTION_NOT_EMPTY,
abstractMethodName));
}
}
/**
* Visits a NAME node.
*
* @param t The node traversal object that supplies context, such as the
* scope chain to use in name lookups as well as error reporting.
* @param n The node being visited.
* @param parent The parent of the node n.
* @return whether the node is typeable or not
*/
boolean visitName(NodeTraversal t, Node n, Node parent) {
// At this stage, we need to determine whether this is a leaf
// node in an expression (which therefore needs to have a type
// assigned for it) versus some other decorative node that we
// can safely ignore. Function names, arguments (children of LP nodes) and
// variable declarations are ignored.
// TODO(user): remove this short-circuiting in favor of a
// pre order traversal of the FUNCTION, CATCH, LP and VAR nodes.
int parentNodeType = parent.getType();
if (parentNodeType == Token.FUNCTION ||
parentNodeType == Token.CATCH ||
parentNodeType == Token.PARAM_LIST ||
parentNodeType == Token.
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>VAR) {
return false;
}
JSType type = n.getJSType();
if (type == null) {
type = getNativeType(UNKNOWN_TYPE);
Var var = t.getScope().getVar(n.getString());
if (var != null) {
JSType varType = var.getType();
if (varType != null) {
type = varType;
}
}
}
ensureTyped(t, n, type);
return true;
}
/**
* Visits a GETPROP node.
*
* @param t The node traversal object that supplies context, such as the
* scope chain to use in name lookups as well as error reporting.
* @param n The node being visited.
* @param parent The parent of <code>n</code>
*/
private void visitGetProp(NodeTraversal t, Node n, Node parent) {
// GETPROP nodes have an assigned type on their node by the scope creator
// if this is an enum declaration. The only namespaced enum declarations
// that we allow are of the form object.name = ...;
if (n.getJSType() != null && parent.isAssign()) {
return;
}
// obj.prop or obj.method()
// Lots of types can appear on the left, a call to a void function can
// never be on the left. getPropertyType will decide what is acceptable
// and what isn't.
Node property = n.getLastChild();
Node objNode = n.getFirstChild();
JSType childType = getJSType(objNode);
// TODO(user): remove in favor of flagging every property access on
// non-object.
if (!validator.expectNotNullOrUndefined(t, n, childType,
"No properties on this expression", getNativeType(OBJECT_TYPE))) {
ensureTyped(t, n);
return;
}
checkPropertyAccess(childType, property.getString(), t, n);
ensureTyped(t, n);
}
/**
* Emit a warning if we can prove that a property cannot possibly be
* defined on an object. Note the difference between JS and a strictly
* statically typed language: we're checking if the property
* *cannot be defined*, whereas a java compiler would check if the
* property *can be undefined*.
*/
private void checkPropertyAccess(JSType childType, String propName,
NodeTraversal t, Node n) {
// If the property type is unknown, check the object type to see if it
// can ever be defined. We explicitly exclude CHECKED_UNKNOWN (for
// properties where we've checked that it exists, or for properties on
// objects that aren't in this binary).
JSType propType = getJSType(n);
if (propType.equals(typeRegistry.getNativeType(UNKNOWN_TYPE))) {
childType = childType.autobox();
ObjectType objectType = ObjectType.cast(child
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>Type);
if (objectType != null) {
// We special-case object types so that checks on enums can be
// much stricter, and so that we can use hasProperty (which is much
// faster in most cases).
if (!objectType.hasProperty(propName) ||
objectType.equals(typeRegistry.getNativeType(UNKNOWN_TYPE))) {
if (objectType instanceof EnumType) {
report(t, n, INEXISTENT_ENUM_ELEMENT, propName);
} else {
checkPropertyAccessHelper(objectType, propName, t, n);
}
}
} else {
checkPropertyAccessHelper(childType, propName, t, n);
}
}
}
private void checkPropertyAccessHelper(JSType objectType, String propName,
NodeTraversal t, Node n) {
if (!objectType.isEmptyType() &&
reportMissingProperties && !isPropertyTest(n)) {
if (!typeRegistry.canPropertyBeDefined(objectType, propName)) {
report(t, n, INEXISTENT_PROPERTY, propName,
validator.getReadableJSTypeName(n.getFirstChild(), true));
}
}
}
/**
* Determines whether this node is testing for the existence of a property.
* If true, we will not emit warnings about a missing property.
*
* @param getProp The GETPROP being tested.
*/
private boolean isPropertyTest(Node getProp) {
Node parent = getProp.getParent();
switch (parent.getType()) {
case Token.CALL:
return parent.getFirstChild() != getProp &&
compiler.getCodingConvention().isPropertyTestFunction(parent);
case Token.IF:
case Token.WHILE:
case Token.DO:
case Token.FOR:
return NodeUtil.getConditionExpression(parent) == getProp;
case Token.INSTANCEOF:
case Token.TYPEOF:
return true;
case Token.AND:
case Token.HOOK:
return parent.getFirstChild() == getProp;
case Token.NOT:
return parent.getParent().isOr() &&
parent.getParent().getFirstChild() == parent;
}
return false;
}
/**
* Visits a GETELEM node.
*
* @param t The node traversal object that supplies context, such as the
* scope chain to use in name lookups as well as error reporting.
* @param n The node being visited.
*/
private void visitGetElem(NodeTraversal t, Node n) {
Node left = n.getFirstChild();
Node right = n.getLastChild();
validator.expectIndexMatch(t, n, getJSType(left), getJSType(right));
ensureTyped(t, n);
}
/**
* Visits a VAR node.
*
* @param t The node traversal object that supplies context, such as the
* scope chain to use in name lookups
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> as well as error reporting.
* @param n The node being visited.
*/
private void visitVar(NodeTraversal t, Node n) {
// TODO(nicksantos): Fix this so that the doc info always shows up
// on the NAME node. We probably want to wait for the parser
// merge to fix this.
JSDocInfo varInfo = n.hasOneChild() ? n.getJSDocInfo() : null;
for (Node name : n.children()) {
Node value = name.getFirstChild();
// A null var would indicate a bug in the scope creation logic.
Var var = t.getScope().getVar(name.getString());
if (value != null) {
JSType valueType = getJSType(value);
JSType nameType = var.getType();
nameType = (nameType == null) ? getNativeType(UNKNOWN_TYPE) : nameType;
JSDocInfo info = name.getJSDocInfo();
if (info == null) {
info = varInfo;
}
checkEnumAlias(t, info, value);
if (var.isTypeInferred()) {
ensureTyped(t, name, valueType);
} else {
validator.expectCanAssignTo(
t, value, valueType, nameType, "initializing variable");
}
}
}
}
/**
* Visits a NEW node.
*/
private void visitNew(NodeTraversal t, Node n) {
Node constructor = n.getFirstChild();
JSType type = getJSType(constructor).restrictByNotNullOrUndefined();
if (type.isConstructor() || type.isEmptyType() || type.isUnknownType()) {
FunctionType fnType = type.toMaybeFunctionType();
if (fnType != null) {
visitParameterList(t, n, fnType);
ensureTyped(t, n, fnType.getInstanceType());
} else {
ensureTyped(t, n);
}
} else {
report(t, n, NOT_A_CONSTRUCTOR);
ensureTyped(t, n);
}
}
/**
* Check whether there's any property conflict for for a particular super
* interface
* @param t The node traversal object that supplies context
* @param n The node being visited
* @param functionName The function name being checked
* @param properties The property names in the super interfaces that have
* been visited
* @param currentProperties The property names in the super interface
* that have been visited
* @param interfaceType The super interface that is being visited
*/
private void checkInterfaceConflictProperties(NodeTraversal t, Node n,
String functionName, HashMap<String, ObjectType> properties,
HashMap<String, ObjectType> currentProperties,
ObjectType interfaceType) {
Set<String> currentPropertyNames = interfaceType.getPropertyNames();
for (String name : currentPropertyNames) {
ObjectType oType = properties.get(name);
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> 1) {
// Only check when extending more than one interfaces
HashMap<String, ObjectType> properties
= new HashMap<String, ObjectType>();
HashMap<String, ObjectType> currentProperties
= new HashMap<String, ObjectType>();
for (ObjectType interfaceType : functionType.getExtendedInterfaces()) {
currentProperties.clear();
checkInterfaceConflictProperties(t, n, functionPrivateName,
properties, currentProperties, interfaceType);
properties.putAll(currentProperties);
}
}
}
}
/**
* Visits a CALL node.
*
* @param t The node traversal object that supplies context, such as the
* scope chain to use in name lookups as well as error reporting.
* @param n The node being visited.
*/
private void visitCall(NodeTraversal t, Node n) {
Node child = n.getFirstChild();
JSType childType = getJSType(child).restrictByNotNullOrUndefined();
if (!childType.canBeCalled()) {
report(t, n, NOT_CALLABLE, childType.toString());
ensureTyped(t, n);
return;
}
// A couple of types can be called as if they were functions.
// If it is a function type, then validate parameters.
if (childType.isFunctionType()) {
FunctionType functionType = childType.toMaybeFunctionType();
boolean isExtern = false;
JSDocInfo functionJSDocInfo = functionType.getJSDocInfo();
if( functionJSDocInfo != null &&
functionJSDocInfo.getAssociatedNode() != null) {
isExtern = functionJSDocInfo.getAssociatedNode().isFromExterns();
}
// Non-native constructors should not be called directly
// unless they specify a return type and are defined
// in an extern.
if (functionType.isConstructor() &&
!functionType.isNativeObjectType() &&
(functionType.getReturnType().isUnknownType() ||
functionType.getReturnType().isVoidType() ||
!isExtern)) {
report(t, n, CONSTRUCTOR_NOT_CALLABLE, childType.toString());
}
// Functions with explicit 'this' types must be called in a GETPROP
// or GETELEM.
if (functionType.isOrdinaryFunction() &&
!functionType.getTypeOfThis().isUnknownType() &&
!functionType.getTypeOfThis().isNativeObjectType() &&
!(child.isGetElem() ||
child.isGetProp())) {
report(t, n, EXPECTED_THIS_TYPE, functionType.toString());
}
visitParameterList(t, n, functionType);
ensureTyped(t, n, functionType.getReturnType());
} else {
ensureTyped(t, n);
}
// TODO: Add something to check for calls of RegExp objects, which is not
// supported by IE. Either say something about the return type or
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> returnType,
"inconsistent return type");
}
}
/**
* This function unifies the type checking involved in the core binary
* operators and the corresponding assignment operators. The representation
* used internally is such that common code can handle both kinds of
* operators easily.
*
* @param op The operator.
* @param t The traversal object, needed to report errors.
* @param n The node being checked.
*/
private void visitBinaryOperator(int op, NodeTraversal t, Node n) {
Node left = n.getFirstChild();
JSType leftType = getJSType(left);
Node right = n.getLastChild();
JSType rightType = getJSType(right);
switch (op) {
case Token.ASSIGN_LSH:
case Token.ASSIGN_RSH:
case Token.LSH:
case Token.RSH:
case Token.ASSIGN_URSH:
case Token.URSH:
if (!leftType.matchesInt32Context()) {
report(t, left, BIT_OPERATION,
NodeUtil.opToStr(n.getType()), leftType.toString());
}
if (!rightType.matchesUint32Context()) {
report(t, right, BIT_OPERATION,
NodeUtil.opToStr(n.getType()), rightType.toString());
}
break;
case Token.ASSIGN_DIV:
case Token.ASSIGN_MOD:
case Token.ASSIGN_MUL:
case Token.ASSIGN_SUB:
case Token.DIV:
case Token.MOD:
case Token.MUL:
case Token.SUB:
validator.expectNumber(t, left, leftType, "left operand");
validator.expectNumber(t, right, rightType, "right operand");
break;
case Token.ASSIGN_BITAND:
case Token.ASSIGN_BITXOR:
case Token.ASSIGN_BITOR:
case Token.BITAND:
case Token.BITXOR:
case Token.BITOR:
validator.expectBitwiseable(t, left, leftType,
"bad left operand to bitwise operator");
validator.expectBitwiseable(t, right, rightType,
"bad right operand to bitwise operator");
break;
case Token.ASSIGN_ADD:
case Token.ADD:
break;
default:
report(t, n, UNEXPECTED_TOKEN, Token.name(op));
}
ensureTyped(t, n);
}
/**
* <p>Checks enum aliases.
*
* <p>We verify that the enum element type of the enum used
* for initialization is a subtype of the enum element type of
* the enum the value is being copied in.</p>
*
* <p>Example:</p>
* <pre>var myEnum = myOtherEnum;</pre>
*
* <p>Enum aliases are irregular, so we need special code for this :(</p>
*
*
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> @param value the value used for initialization of the enum
*/
private void checkEnumAlias(
NodeTraversal t, JSDocInfo declInfo, Node value) {
if (declInfo == null || !declInfo.hasEnumParameterType()) {
return;
}
JSType valueType = getJSType(value);
if (!valueType.isEnumType()) {
return;
}
EnumType valueEnumType = valueType.toMaybeEnumType();
JSType valueEnumPrimitiveType =
valueEnumType.getElementsType().getPrimitiveType();
validator.expectCanAssignTo(t, value, valueEnumPrimitiveType,
declInfo.getEnumParameterType().evaluate(t.getScope(), typeRegistry),
"incompatible enum element types");
}
/**
* This method gets the JSType from the Node argument and verifies that it is
* present.
*/
private JSType getJSType(Node n) {
JSType jsType = n.getJSType();
if (jsType == null) {
// TODO(nicksantos): This branch indicates a compiler bug, not worthy of
// halting the compilation but we should log this and analyze to track
// down why it happens. This is not critical and will be resolved over
// time as the type checker is extended.
return getNativeType(UNKNOWN_TYPE);
} else {
return jsType;
}
}
// TODO(nicksantos): TypeCheck should never be attaching types to nodes.
// All types should be attached by TypeInference. This is not true today
// for legacy reasons. There are a number of places where TypeInference
// doesn't attach a type, as a signal to TypeCheck that it needs to check
// that node's type.
/**
* Ensure that the given node has a type. If it does not have one,
* attach the UNKNOWN_TYPE.
*/
private void ensureTyped(NodeTraversal t, Node n) {
ensureTyped(t, n, getNativeType(UNKNOWN_TYPE));
}
private void ensureTyped(NodeTraversal t, Node n, JSTypeNative type) {
ensureTyped(t, n, getNativeType(type));
}
/**
* Enforces type casts, and ensures the node is typed.
*
* A cast in the way that we use it in JSDoc annotations never
* alters the generated code and therefore never can induce any runtime
* operation. What this means is that a 'cast' is really just a compile
* time constraint on the underlying value. In the future, we may add
* support for run-time casts for compiled tests.
*
* To ensure some shred of sanity, we enforce the notion that the
* type you are casting to may only meaningfully be a narrower type
* than the underlying declared type. We also invalidate optimizations
* on bad type casts.
*
* @param t The traversal object needed to report errors.
* @
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>param n The node getting a type assigned to it.
* @param type The type to be assigned.
*/
private void ensureTyped(NodeTraversal t, Node n, JSType type) {
// Make sure FUNCTION nodes always get function type.
Preconditions.checkState(!n.isFunction() ||
type.isFunctionType() ||
type.isUnknownType());
JSDocInfo info = n.getJSDocInfo();
if (info != null) {
if (info.hasType()) {
JSType infoType = info.getType().evaluate(t.getScope(), typeRegistry);
validator.expectCanCast(t, n, infoType, type);
type = infoType;
}
if (info.isImplicitCast() && !inExterns) {
String propName = n.isGetProp() ?
n.getLastChild().getString() : "(missing)";
compiler.report(
t.makeError(n, ILLEGAL_IMPLICIT_CAST, propName));
}
}
if (n.getJSType() == null) {
n.setJSType(type);
}
}
/**
* Returns the percentage of nodes typed by the type checker.
* @return a number between 0.0 and 100.0
*/
double getTypedPercent() {
int total = nullCount + unknownCount + typedCount;
if (total == 0) {
return 0.0;
} else {
return (100.0 * typedCount) / total;
}
}
private JSType getNativeType(JSTypeNative typeId) {
return typeRegistry.getNativeType(typeId);
}
}
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>/*
* Copyright 2010 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.javascript.jscomp.NodeTraversal.AbstractPostOrderCallback;
import com.google.javascript.jscomp.regex.RegExpTree;
import com.google.javascript.rhino.Token;
import com.google.javascript.rhino.Node;
/**
* Look for references to the global RegExp object that would cause
* regular expressions to be unoptimizable, and checks that regular expressions
* are syntactically valid.
*
* @author johnlenz@google.com (John Lenz)
*/
class CheckRegExp extends AbstractPostOrderCallback implements CompilerPass {
static final DiagnosticType REGEXP_REFERENCE =
DiagnosticType.warning("JSC_REGEXP_REFERENCE",
"References to the global RegExp object prevents " +
"optimization of regular expressions.");
static final DiagnosticType MALFORMED_REGEXP = DiagnosticType.warning(
"JSC_MALFORMED_REGEXP",
"Malformed Regular Expression: {0}");
private final AbstractCompiler compiler;
private boolean globalRegExpPropertiesUsed = false;
public boolean isGlobalRegExpPropertiesUsed() {
return globalRegExpPropertiesUsed;
}
public CheckRegExp(AbstractCompiler compiler) {
this.compiler = compiler;
}
@Override
public void process(Node externs, Node root) {
NodeTraversal.traverse(compiler, root, this);
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (NodeUtil.isReferenceName(n)) {
String name = n.getString();
if (name.equals("RegExp") && t.getScope().getVar(name) == null) {
int parentType = parent.getType();
boolean first = (n == parent.getFirstChild());
if (!((parentType == Token.NEW && first)
|| (parentType == Token.CALL && first)
|| (parentType == Token.INSTANCEOF && !first))) {
t.report(n, REGEXP_REFERENCE);
globalRegExpPropertiesUsed = true;
}
}
// Check the syntax of regular expression patterns.
} else if (n.isRegExp()) {
String pattern = n.getFirstChild().getString();
String flags = n.getChildCount() == 2
?
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> Config config;
private final ErrorReporter errorReporter;
private final TransformDispatcher transformDispatcher;
private static final ImmutableSet<String> ALLOWED_DIRECTIVES =
ImmutableSet.of("use strict");
private static final ImmutableSet<String> ES5_RESERVED_KEYWORDS =
ImmutableSet.of(
// From Section 7.6.1.2
"class", "const", "enum", "export", "extends", "import", "super");
private static final ImmutableSet<String> ES5_STRICT_RESERVED_KEYWORDS =
ImmutableSet.of(
// From Section 7.6.1.2
"class", "const", "enum", "export", "extends", "import", "super",
"implements", "interface", "let", "package", "private", "protected",
"public", "static", "yield");
private final Set<String> reservedKeywords;
private final Set<Comment> parsedComments = Sets.newHashSet();
// @license text gets appended onto the fileLevelJsDocBuilder as found,
// and stored in JSDocInfo for placeholder node.
Node rootNodeJsDocHolder = new Node(Token.SCRIPT);
Node.FileLevelJsDocBuilder fileLevelJsDocBuilder =
rootNodeJsDocHolder.getJsDocBuilderForNode();
JSDocInfo fileOverviewInfo = null;
// Use a template node for properties set on all nodes to minimize the
// memory footprint associated with these.
private Node templateNode;
// TODO(johnlenz): Consider creating a template pool for ORIGINALNAME_PROP.
private IRFactory(String sourceString,
StaticSourceFile sourceFile,
Config config,
ErrorReporter errorReporter) {
this.sourceString = sourceString;
this.sourceFile = sourceFile;
// Sometimes this will be null in tests.
this.sourceName = sourceFile == null ? null : sourceFile.getName();
this.config = config;
this.errorReporter = errorReporter;
this.transformDispatcher = new TransformDispatcher();
// The template node properties are applied to all nodes in this transform.
this.templateNode = createTemplateNode();
switch (config.languageMode) {
case ECMASCRIPT3:
// Reserved words are handled by the Rhino parser.
reservedKeywords = null;
break;
case ECMASCRIPT5:
reservedKeywords = ES5_RESERVED_KEYWORDS;
break;
case ECMASCRIPT5_STRICT:
reservedKeywords = ES5_STRICT_RESERVED_KEYWORDS;
break;
default:
throw new IllegalStateException("unknown language mode");
}
}
// Create a template node to use as a source of common attributes, this allows
// the prop structure to be shared among all the node from this source file.
// This reduces the cost of these properties to O(nodes) to O(files).
private Node createTemplateNode() {
// The Node type choice is arbitrary.
Node templateNode =
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> new Node(Token.SCRIPT);
templateNode.setStaticSourceFile(sourceFile);
return templateNode;
}
public static Node transformTree(AstRoot node,
StaticSourceFile sourceFile,
String sourceString,
Config config,
ErrorReporter errorReporter) {
IRFactory irFactory = new IRFactory(sourceString, sourceFile,
config, errorReporter);
Node irNode = irFactory.transform(node);
if (node.getComments() != null) {
for (Comment comment : node.getComments()) {
if (comment.getCommentType() == CommentType.JSDOC &&
!irFactory.parsedComments.contains(comment)) {
irFactory.handlePossibleFileOverviewJsDoc(comment, irNode);
} else if (comment.getCommentType() == CommentType.BLOCK_COMMENT) {
irFactory.handleBlockComment(comment);
}
}
}
irFactory.setFileOverviewJsDoc(irNode);
return irNode;
}
private void setFileOverviewJsDoc(Node irNode) {
// Only after we've seen all @fileoverview entries, attach the
// last one to the root node, and copy the found license strings
// to that node.
JSDocInfo rootNodeJsDoc = rootNodeJsDocHolder.getJSDocInfo();
if (rootNodeJsDoc != null) {
irNode.setJSDocInfo(rootNodeJsDoc);
rootNodeJsDoc.setAssociatedNode(irNode);
}
if (fileOverviewInfo != null) {
if ((irNode.getJSDocInfo() != null) &&
(irNode.getJSDocInfo().getLicense() != null)) {
fileOverviewInfo.setLicense(irNode.getJSDocInfo().getLicense());
}
irNode.setJSDocInfo(fileOverviewInfo);
fileOverviewInfo.setAssociatedNode(irNode);
}
}
private Node transformBlock(AstNode node) {
Node irNode = transform(node);
if (!irNode.isBlock()) {
if (irNode.isEmpty()) {
irNode.setType(Token.BLOCK);
irNode.setWasEmptyNode(true);
} else {
Node newBlock = newNode(Token.BLOCK, irNode);
newBlock.setLineno(irNode.getLineno());
newBlock.setCharno(irNode.getCharno());
maybeSetLengthFrom(newBlock, node);
irNode = newBlock;
}
}
return irNode;
}
/**
* Check to see if the given block comment looks like it should be JSDoc.
*/
private void handleBlockComment(Comment comment) {
String value = comment.getValue();
if (value.indexOf("/* @") != -1 ||
value.indexOf("\n * @") != -1) {
errorReporter.warning(
SUSPICIOUS_COMMENT_WARNING,
sourceName
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>,
comment.getLineno(), "", 0);
}
}
/**
* @return true if the jsDocParser represents a fileoverview.
*/
private boolean handlePossibleFileOverviewJsDoc(
JsDocInfoParser jsDocParser) {
if (jsDocParser.getFileOverviewJSDocInfo() != fileOverviewInfo) {
fileOverviewInfo = jsDocParser.getFileOverviewJSDocInfo();
return true;
}
return false;
}
private void handlePossibleFileOverviewJsDoc(Comment comment, Node irNode) {
JsDocInfoParser jsDocParser = createJsDocInfoParser(comment, irNode);
parsedComments.add(comment);
handlePossibleFileOverviewJsDoc(jsDocParser);
}
private JSDocInfo handleJsDoc(AstNode node, Node irNode) {
Comment comment = node.getJsDocNode();
if (comment != null) {
JsDocInfoParser jsDocParser = createJsDocInfoParser(comment, irNode);
parsedComments.add(comment);
if (!handlePossibleFileOverviewJsDoc(jsDocParser)) {
return jsDocParser.retrieveAndResetParsedJSDocInfo();
}
}
return null;
}
private Node transform(AstNode node) {
Node irNode = justTransform(node);
JSDocInfo jsDocInfo = handleJsDoc(node, irNode);
if (jsDocInfo != null) {
irNode.setJSDocInfo(jsDocInfo);
}
setSourceInfo(irNode, node);
return irNode;
}
private Node transformNameAsString(Name node) {
Node irNode = transformDispatcher.processName(node, true);
JSDocInfo jsDocInfo = handleJsDoc(node, irNode);
if (jsDocInfo != null) {
irNode.setJSDocInfo(jsDocInfo);
}
setSourceInfo(irNode, node);
return irNode;
}
private Node transformNumberAsString(NumberLiteral literalNode) {
Node irNode = newStringNode(getStringValue(literalNode.getNumber()));
JSDocInfo jsDocInfo = handleJsDoc(literalNode, irNode);
if (jsDocInfo != null) {
irNode.setJSDocInfo(jsDocInfo);
}
setSourceInfo(irNode, literalNode);
return irNode;
}
private static String getStringValue(double value) {
long longValue = (long) value;
// Return "1" instead of "1.0"
if (longValue == value) {
return Long.toString(longValue);
} else {
return Double.toString(value);
}
}
private void setSourceInfo(Node irNode, AstNode node) {
if (irNode.getLineno() == -1) {
// If we didn't already set the line, then set it now. This avoids
// cases like ParenthesizedExpression
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> where we just return a previous
// node, but don't want the new node to get its parent's line number.
int lineno = node.getLineno();
irNode.setLineno(lineno);
int charno = position2charno(node.getAbsolutePosition());
irNode.setCharno(charno);
maybeSetLengthFrom(irNode, node);
}
}
/**
* Creates a JsDocInfoParser and parses the JsDoc string.
*
* Used both for handling individual JSDoc comments and for handling
* file-level JSDoc comments (@fileoverview and @license).
*
* @param node The JsDoc Comment node to parse.
* @param irNode
* @return A JsDocInfoParser. Will contain either fileoverview JsDoc, or
* normal JsDoc, or no JsDoc (if the method parses to the wrong level).
*/
private JsDocInfoParser createJsDocInfoParser(Comment node, Node irNode) {
String comment = node.getValue();
int lineno = node.getLineno();
int position = node.getAbsolutePosition();
// The JsDocInfoParser expects the comment without the initial '/**'.
int numOpeningChars = 3;
JsDocInfoParser jsdocParser =
new JsDocInfoParser(
new JsDocTokenStream(comment.substring(numOpeningChars),
lineno,
position2charno(position) + numOpeningChars),
node,
irNode,
config,
errorReporter);
jsdocParser.setFileLevelJsDocBuilder(fileLevelJsDocBuilder);
jsdocParser.setFileOverviewJSDocInfo(fileOverviewInfo);
jsdocParser.parse();
return jsdocParser;
}
// Set the length on the node if we're in IDE mode.
private void maybeSetLengthFrom(Node node, AstNode source) {
if (config.isIdeMode) {
node.setLength(source.getLength());
}
}
private int position2charno(int position) {
int lineIndex = sourceString.lastIndexOf('\n', position);
if (lineIndex == -1) {
return position;
} else {
// Subtract one for initial position being 0.
return position - lineIndex - 1;
}
}
private Node justTransform(AstNode node) {
return transformDispatcher.process(node);
}
private class TransformDispatcher extends TypeSafeDispatcher<Node> {
private Node processGeneric(
com.google.javascript.rhino.head.Node n) {
Node node = newNode(transformTokenType(n.getType()));
for (com.google.javascript.rhino.head.Node child : n) {
node.addChildToBack(transform((AstNode)child));
}
return node;
}
/**
* Transforms the given node and then sets its type to Token.STRING if it
* was Token.NAME. If its type was already Token
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>Node n) {
if (n == null) return false;
int nType = n.getType();
return nType == Token.EXPR_RESULT &&
n.getFirstChild().isString() &&
ALLOWED_DIRECTIVES.contains(n.getFirstChild().getString());
}
@Override
Node processBlock(Block blockNode) {
return processGeneric(blockNode);
}
@Override
Node processBreakStatement(BreakStatement statementNode) {
Node node = newNode(Token.BREAK);
if (statementNode.getBreakLabel() != null) {
Node labelName = transform(statementNode.getBreakLabel());
// Change the NAME to LABEL_NAME
labelName.setType(Token.LABEL_NAME);
node.addChildToBack(labelName);
}
return node;
}
@Override
Node processCatchClause(CatchClause clauseNode) {
AstNode catchVar = clauseNode.getVarName();
Node node = newNode(Token.CATCH, transform(catchVar));
if (clauseNode.getCatchCondition() != null) {
errorReporter.error(
"Catch clauses are not supported",
sourceName,
clauseNode.getCatchCondition().getLineno(), "", 0);
}
node.addChildToBack(transformBlock(clauseNode.getBody()));
return node;
}
@Override
Node processConditionalExpression(ConditionalExpression exprNode) {
return newNode(
Token.HOOK,
transform(exprNode.getTestExpression()),
transform(exprNode.getTrueExpression()),
transform(exprNode.getFalseExpression()));
}
@Override
Node processContinueStatement(ContinueStatement statementNode) {
Node node = newNode(Token.CONTINUE);
if (statementNode.getLabel() != null) {
Node labelName = transform(statementNode.getLabel());
// Change the NAME to LABEL_NAME
labelName.setType(Token.LABEL_NAME);
node.addChildToBack(labelName);
}
return node;
}
@Override
Node processDoLoop(DoLoop loopNode) {
return newNode(
Token.DO,
transformBlock(loopNode.getBody()),
transform(loopNode.getCondition()));
}
@Override
Node processElementGet(ElementGet getNode) {
return newNode(
Token.GETELEM,
transform(getNode.getTarget()),
transform(getNode.getElement()));
}
@Override
Node processEmptyExpression(EmptyExpression exprNode) {
Node node = newNode(Token.EMPTY);
return node;
}
@Override
Node processEmptyStatement(EmptyStatement exprNode) {
Node node = newNode(Token.EMPTY);
return node;
}
@Override
Node processExpressionStatement(ExpressionStatement statementNode) {
Node node = newNode(transformTokenType(statementNode.getType()));
node.addChildToBack(transform(statementNode.getExpression()));
return node;
}
@Override
Node processForInLoop(ForInLoop loopNode) {
if (loopNode.
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>isForEach()) {
errorReporter.error(
"unsupported language extension: for each",
sourceName,
loopNode.getLineno(), "", 0);
// Return the bare minimum to put the AST in a valid state.
return newNode(Token.EXPR_RESULT, Node.newNumber(0));
}
return newNode(
Token.FOR,
transform(loopNode.getIterator()),
transform(loopNode.getIteratedObject()),
transformBlock(loopNode.getBody()));
}
@Override
Node processForLoop(ForLoop loopNode) {
Node node = newNode(
Token.FOR,
transform(loopNode.getInitializer()),
transform(loopNode.getCondition()),
transform(loopNode.getIncrement()));
node.addChildToBack(transformBlock(loopNode.getBody()));
return node;
}
@Override
Node processFunctionCall(FunctionCall callNode) {
Node node = newNode(transformTokenType(callNode.getType()),
transform(callNode.getTarget()));
for (AstNode child : callNode.getArguments()) {
node.addChildToBack(transform(child));
}
node.setLineno(node.getFirstChild().getLineno());
node.setCharno(node.getFirstChild().getCharno());
maybeSetLengthFrom(node, callNode);
return node;
}
@Override
Node processFunctionNode(FunctionNode functionNode) {
Name name = functionNode.getFunctionName();
Boolean isUnnamedFunction = false;
if (name == null) {
int functionType = functionNode.getFunctionType();
if (functionType != FunctionNode.FUNCTION_EXPRESSION) {
errorReporter.error(
"unnamed function statement",
sourceName,
functionNode.getLineno(), "", 0);
// Return the bare minimum to put the AST in a valid state.
return newNode(Token.EXPR_RESULT, Node.newNumber(0));
}
name = new Name();
name.setIdentifier("");
isUnnamedFunction = true;
}
Node node = newNode(Token.FUNCTION);
Node newName = transform(name);
if (isUnnamedFunction) {
// Old Rhino tagged the empty name node with the line number of the
// declaration.
newName.setLineno(functionNode.getLineno());
// TODO(bowdidge) Mark line number of paren correctly.
// Same problem as below - the left paren might not be on the
// same line as the function keyword.
int lpColumn = functionNode.getAbsolutePosition() +
functionNode.getLp();
newName.setCharno(position2charno(lpColumn));
maybeSetLengthFrom(newName, name);
}
node.addChildToBack(newName);
Node lp = newNode(Token.PARAM_LIST);
// The left paren's complicated because it's not represented by an
// AstNode, so there's nothing that has the actual line number that it
// appeared
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> on. We know the paren has to appear on the same line as the
// function name (or else a semicolon will be inserted.) If there's no
// function name, assume the paren was on the same line as the function.
// TODO(bowdidge): Mark line number of paren correctly.
Name fnName = functionNode.getFunctionName();
if (fnName != null) {
lp.setLineno(fnName.getLineno());
} else {
lp.setLineno(functionNode.getLineno());
}
int lparenCharno = functionNode.getLp() +
functionNode.getAbsolutePosition();
lp.setCharno(position2charno(lparenCharno));
for (AstNode param : functionNode.getParams()) {
Node paramNode = transform(param);
// When in ideMode Rhino can generate a param list with only a single
// ErrorNode. This is transformed into an EMPTY node. Drop this node in
// ideMode to keep the AST in a valid state.
if (paramNode.isName()) {
lp.addChildToBack(paramNode);
} else {
// We expect this in ideMode or when there is an error handling
// destructuring parameter assignments which aren't supported
// (an error has already been reported).
Preconditions.checkState(
config.isIdeMode
|| paramNode.isObjectLit()
|| paramNode.isArrayLit());
}
}
node.addChildToBack(lp);
Node bodyNode = transform(functionNode.getBody());
if (!bodyNode.isBlock()) {
// When in ideMode Rhino tries to parse some constructs the compiler
// doesn't support, repair it here. see Rhino's
// Parser#parseFunctionBodyExpr.
Preconditions.checkState(config.isIdeMode);
bodyNode = IR.block();
}
parseDirectives(bodyNode);
node.addChildToBack(bodyNode);
return node;
}
@Override
Node processIfStatement(IfStatement statementNode) {
Node node = newNode(Token.IF);
node.addChildToBack(transform(statementNode.getCondition()));
node.addChildToBack(transformBlock(statementNode.getThenPart()));
if (statementNode.getElsePart() != null) {
node.addChildToBack(transformBlock(statementNode.getElsePart()));
}
return node;
}
@Override
Node processInfixExpression(InfixExpression exprNode) {
Node n = newNode(
transformTokenType(exprNode.getType()),
transform(exprNode.getLeft()),
transform(exprNode.getRight()));
n.setLineno(exprNode.getLineno());
n.setCharno(position2charno(exprNode.getAbsolutePosition()));
maybeSetLengthFrom(n, exprNode);
return n;
}
@Override
Node processKeywordLiteral(KeywordLiteral literalNode)
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> {
return newNode(transformTokenType(literalNode.getType()));
}
@Override
Node processLabel(Label labelNode) {
return newStringNode(Token.LABEL_NAME, labelNode.getName());
}
@Override
Node processLabeledStatement(LabeledStatement statementNode) {
Node node = newNode(Token.LABEL);
Node prev = null;
Node cur = node;
for (Label label : statementNode.getLabels()) {
if (prev != null) {
prev.addChildToBack(cur);
}
cur.addChildToBack(transform(label));
cur.setLineno(label.getLineno());
maybeSetLengthFrom(cur, label);
int clauseAbsolutePosition =
position2charno(label.getAbsolutePosition());
cur.setCharno(clauseAbsolutePosition);
prev = cur;
cur = newNode(Token.LABEL);
}
prev.addChildToBack(transform(statementNode.getStatement()));
return node;
}
@Override
Node processName(Name nameNode) {
return processName(nameNode, false);
}
Node processName(Name nameNode, boolean asString) {
if (asString) {
return newStringNode(Token.STRING, nameNode.getIdentifier());
} else {
if (isReservedKeyword(nameNode.getIdentifier())) {
errorReporter.error(
"identifier is a reserved word",
sourceName,
nameNode.getLineno(), "", 0);
}
return newStringNode(Token.NAME, nameNode.getIdentifier());
}
}
/**
* @return Whether the
*/
private boolean isReservedKeyword(String identifier) {
return reservedKeywords != null && reservedKeywords.contains(identifier);
}
@Override
Node processNewExpression(NewExpression exprNode) {
return processFunctionCall(exprNode);
}
@Override
Node processNumberLiteral(NumberLiteral literalNode) {
return newNumberNode(literalNode.getNumber());
}
@Override
Node processObjectLiteral(ObjectLiteral literalNode) {
if (literalNode.isDestructuring()) {
reportDestructuringAssign(literalNode);
}
Node node = newNode(Token.OBJECTLIT);
for (ObjectProperty el : literalNode.getElements()) {
if (config.languageMode == LanguageMode.ECMASCRIPT3) {
if (el.isGetter()) {
reportGetter(el);
continue;
} else if (el.isSetter()) {
reportSetter(el);
continue;
}
}
Node key = transformAsString(el.getLeft());
key.setType(Token.STRING_KEY);
Node value = transform(el.getRight());
if (el.isGetter()) {
key.setType(Token.GETTER_DEF);
Preconditions.checkState(value.isFunction());
if (getFnParamNode(value).hasChildren()) {
reportGetterParam(el.getLeft());
}
} else
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> block.setLineno(cc.getLineno());
maybeSetLengthFrom(block, cc);
lineSet = true;
}
block.addChildToBack(transform(cc));
}
node.addChildToBack(block);
AstNode finallyBlock = statementNode.getFinallyBlock();
if (finallyBlock != null) {
node.addChildToBack(transformBlock(finallyBlock));
}
// If we didn't set the line on the catch clause, then
// we've got an empty catch clause. Set its line to be the same
// as the finally block (to match Old Rhino's behavior.)
if ((lineSet == false) && (finallyBlock != null)) {
block.setLineno(finallyBlock.getLineno());
maybeSetLengthFrom(block, finallyBlock);
}
return node;
}
@Override
Node processUnaryExpression(UnaryExpression exprNode) {
int type = transformTokenType(exprNode.getType());
Node operand = transform(exprNode.getOperand());
if (type == Token.NEG && operand.isNumber()) {
operand.setDouble(-operand.getDouble());
return operand;
} else {
if (type == Token.DELPROP &&
!(operand.isGetProp() ||
operand.isGetElem() ||
operand.isName())) {
String msg =
"Invalid delete operand. Only properties can be deleted.";
errorReporter.error(
msg,
sourceName,
operand.getLineno(), "", 0);
} else if (type == Token.INC || type == Token.DEC) {
if (!validAssignmentTarget(operand)) {
String msg = (type == Token.INC)
? "invalid increment target"
: "invalid decrement target";
errorReporter.error(
msg,
sourceName,
operand.getLineno(), "", 0);
}
}
Node node = newNode(type, operand);
if (exprNode.isPostfix()) {
node.putBooleanProp(Node.INCRDECR_PROP, true);
}
return node;
}
}
private boolean validAssignmentTarget(Node target) {
switch (target.getType()) {
case Token.NAME:
case Token.GETPROP:
case Token.GETELEM:
return true;
}
return false;
}
@Override
Node processVariableDeclaration(VariableDeclaration declarationNode) {
if (!config.acceptConstKeyword && declarationNode.getType() ==
com.google.javascript.rhino.head.Token.CONST) {
processIllegalToken(declarationNode);
}
Node node = newNode(Token.VAR);
for (VariableInitializer child : declarationNode.getVariables()) {
node.addChildToBack(transform(child));
}
return node;
}
@Override
Node processVariableInitializer(VariableInitializer initializerNode) {
Node node = transform(initializerNode.getTarget());
if (initializerNode.getInitializer() != null) {
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>
Node initalizer = transform(initializerNode.getInitializer());
node.addChildToBack(initalizer);
}
return node;
}
@Override
Node processWhileLoop(WhileLoop loopNode) {
return newNode(
Token.WHILE,
transform(loopNode.getCondition()),
transformBlock(loopNode.getBody()));
}
@Override
Node processWithStatement(WithStatement statementNode) {
return newNode(
Token.WITH,
transform(statementNode.getExpression()),
transformBlock(statementNode.getStatement()));
}
@Override
Node processIllegalToken(AstNode node) {
errorReporter.error(
"Unsupported syntax: " +
com.google.javascript.rhino.head.Token.typeToName(
node.getType()),
sourceName,
node.getLineno(), "", 0);
return newNode(Token.EMPTY);
}
void reportDestructuringAssign(AstNode node) {
errorReporter.error(
"destructuring assignment forbidden",
sourceName,
node.getLineno(), "", 0);
}
void reportGetter(AstNode node) {
errorReporter.error(
GETTER_ERROR_MESSAGE,
sourceName,
node.getLineno(), "", 0);
}
void reportSetter(AstNode node) {
errorReporter.error(
SETTER_ERROR_MESSAGE,
sourceName,
node.getLineno(), "", 0);
}
void reportGetterParam(AstNode node) {
errorReporter.error(
"getters may not have parameters",
sourceName,
node.getLineno(), "", 0);
}
void reportSetterParam(AstNode node) {
errorReporter.error(
"setters must have exactly one parameter",
sourceName,
node.getLineno(), "", 0);
}
}
private static int transformTokenType(int token) {
switch (token) {
case com.google.javascript.rhino.head.Token.RETURN:
return Token.RETURN;
case com.google.javascript.rhino.head.Token.BITOR:
return Token.BITOR;
case com.google.javascript.rhino.head.Token.BITXOR:
return Token.BITXOR;
case com.google.javascript.rhino.head.Token.BITAND:
return Token.BITAND;
case com.google.javascript.rhino.head.Token.EQ:
return Token.EQ;
case com.google.javascript.rhino.head.Token.NE:
return Token.NE;
case com.google.javascript.rhino.head.Token.LT:
return Token.LT;
case com.google.javascript.rhino.head.Token.LE:
return Token.LE;
case com.google.javascript.rhino.head.Token.GT:
return Token.GT;
case com.google.javascript.rhino
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>/*
*
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Rhino code, released
* May 6, 1999.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1997-1999
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Bob Jervis
* Google Inc.
*
* Alternatively, the contents of this file may be used under the terms of
* the GNU General Public License Version 2 or later (the "GPL"), in which
* case the provisions of the GPL are applicable instead of those above. If
* you wish to allow use of your version of this file only under the terms of
* the GPL and not to allow others to use your version of this file under the
* MPL, indicate your decision by deleting the provisions above and replacing
* them with the notice and other provisions required by the GPL. If you do
* not delete the provisions above, a recipient may use your version of this
* file under either the MPL or the GPL.
*
* ***** END LICENSE BLOCK ***** */
package com.google.javascript.rhino.jstype;
import com.google.javascript.rhino.ErrorReporter;
/**
* Value types (null, void, number, boolean, string).
*/
abstract class ValueType extends JSType {
ValueType(JSTypeRegistry registry) {
super(registry);
}
@Override
final JSType resolveInternal(ErrorReporter t, StaticScope<JSType> scope) {
return this;
}
@Override
public boolean hasDisplayName() {
return true;
}
}
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>
JSError.make(filename,
reference.getNode(),
checkLevel,
REDECLARED_VARIABLE, v.name));
break;
}
}
}
if (isUnhoistedNamedFunction && !isDeclaration && isDeclaredInScope) {
// Only allow an unhoisted named function to be used within the
// block it is declared.
for (BasicBlock declaredBlock : blocksWithDeclarations) {
if (!declaredBlock.provablyExecutesBefore(basicBlock)) {
String filename = NodeUtil.getSourceName(reference.getNode());
compiler.report(
JSError.make(filename,
reference.getNode(),
AMBIGUOUS_FUNCTION_DECL, v.name));
break;
}
}
}
if (!isDeclaration && !isDeclaredInScope) {
// Don't check the order of refer in externs files.
if (!reference.getNode().isFromExterns()) {
// Special case to deal with var goog = goog || {}
Node grandparent = reference.getGrandparent();
if (grandparent.isName()
&& grandparent.getString() == v.name) {
continue;
}
// Only generate warnings if the scopes do not match in order
// to deal with possible forward declarations and recursion
if (reference.getScope() == v.scope) {
String filename = NodeUtil.getSourceName(reference.getNode());
compiler.report(
JSError.make(filename,
reference.getNode(),
checkLevel,
UNDECLARED_REFERENCE, v.name));
}
}
}
if (isDeclaration) {
blocksWithDeclarations.add(basicBlock);
isDeclaredInScope = true;
}
}
}
}
}
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>
}
}
return null;
}
/**
* Gets the number of properties of this object.
*/
@Override
public int getPropertiesCount() {
ObjectType implicitPrototype = getImplicitPrototype();
if (implicitPrototype == null) {
return this.properties.size();
}
int localCount = 0;
for (String property : properties.keySet()) {
if (!implicitPrototype.hasProperty(property)) {
localCount++;
}
}
return implicitPrototype.getPropertiesCount() + localCount;
}
@Override
public boolean hasProperty(String propertyName) {
// Unknown types have all properties.
return isUnknownType() || getSlot(propertyName) != null;
}
@Override
public boolean hasOwnProperty(String propertyName) {
return properties.get(propertyName) != null;
}
@Override
public Set<String> getOwnPropertyNames() {
return properties.keySet();
}
@Override
public boolean isPropertyTypeDeclared(String property) {
StaticSlot<JSType> slot = getSlot(property);
if (slot == null) {
return false;
}
return !slot.isTypeInferred();
}
@Override
void collectPropertyNames(Set<String> props) {
for (String prop : properties.keySet()) {
props.add(prop);
}
ObjectType implicitPrototype = getImplicitPrototype();
if (implicitPrototype != null) {
implicitPrototype.collectPropertyNames(props);
}
}
@Override
public boolean isPropertyTypeInferred(String property) {
StaticSlot<JSType> slot = getSlot(property);
if (slot == null) {
return false;
}
return slot.isTypeInferred();
}
@Override
public JSType getPropertyType(String property) {
StaticSlot<JSType> slot = getSlot(property);
if (slot == null) {
return getNativeType(JSTypeNative.UNKNOWN_TYPE);
}
return slot.getType();
}
@Override
public boolean isPropertyInExterns(String propertyName) {
Property p = properties.get(propertyName);
if (p != null) {
return p.isFromExterns();
}
ObjectType implicitPrototype = getImplicitPrototype();
if (implicitPrototype != null) {
return implicitPrototype.isPropertyInExterns(propertyName);
}
return false;
}
@Override
boolean defineProperty(String name, JSType type, boolean inferred,
Node propertyNode) {
if (hasOwnDeclaredProperty(name)) {
return false;
}
Property newProp = new Property(
name, type, inferred, propertyNode);
Property oldProp = properties.get(name);
if (oldProp != null) {
// This is to keep previously inferred JsDoc info, e.g., in a
// replaceScript scenario.
newProp.setJSDocInfo(oldProp.getJSDocInfo());
}
properties
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>.put(name, newProp);
return true;
}
@Override
public boolean removeProperty(String name) {
return properties.remove(name) != null;
}
@Override
public Node getPropertyNode(String propertyName) {
Property p = properties.get(propertyName);
if (p != null) {
return p.getNode();
}
ObjectType implicitPrototype = getImplicitPrototype();
if (implicitPrototype != null) {
return implicitPrototype.getPropertyNode(propertyName);
}
return null;
}
@Override
public JSDocInfo getOwnPropertyJSDocInfo(String propertyName) {
Property p = properties.get(propertyName);
if (p != null) {
return p.getJSDocInfo();
}
return null;
}
@Override
public void setPropertyJSDocInfo(String propertyName, JSDocInfo info) {
if (info != null) {
if (!properties.containsKey(propertyName)) {
// If docInfo was attached, but the type of the property
// was not defined anywhere, then we consider this an explicit
// declaration of the property.
defineInferredProperty(propertyName, getPropertyType(propertyName),
null);
}
// The prototype property is not represented as a normal Property.
// We probably don't want to attach any JSDoc to it anyway.
Property property = properties.get(propertyName);
if (property != null) {
property.setJSDocInfo(info);
}
}
}
@Override
public boolean matchesNumberContext() {
return isNumberObjectType() || isDateType() || isBooleanObjectType() ||
isStringObjectType() || hasOverridenNativeProperty("valueOf");
}
@Override
public boolean matchesStringContext() {
return isTheObjectType() || isStringObjectType() || isDateType() ||
isRegexpType() || isArrayType() || isNumberObjectType() ||
isBooleanObjectType() || hasOverridenNativeProperty("toString");
}
/**
* Given the name of a native object property, checks whether the property is
* present on the object and different from the native one.
*/
private boolean hasOverridenNativeProperty(String propertyName) {
if (isNativeObjectType()) {
return false;
}
JSType propertyType = getPropertyType(propertyName);
ObjectType nativeType =
this.isFunctionType() ?
registry.getNativeObjectType(JSTypeNative.FUNCTION_PROTOTYPE) :
registry.getNativeObjectType(JSTypeNative.OBJECT_PROTOTYPE);
JSType nativePropertyType = nativeType.getPropertyType(propertyName);
return propertyType != nativePropertyType;
}
@Override
public JSType unboxesTo() {
if (isStringObjectType()) {
return getNativeType(JSTypeNative.STRING_TYPE);
} else if (isBooleanObjectType()) {
return getNativeType(JSTypeNative.BOOLEAN_TYPE);
} else if (isNumberObjectType()) {
return getNativeType(JSTypeNative
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> types, so we don't need to check those again.
return false;
}
// record types
if (that.isRecordType()) {
return RecordType.isSubtype(this, that.toMaybeRecordType());
}
// Interfaces
// Find all the interfaces implemented by this class and compare each one
// to the interface instance.
ObjectType thatObj = that.toObjectType();
ObjectType thatCtor = thatObj == null ? null : thatObj.getConstructor();
if (thatCtor != null && thatCtor.isInterface()) {
Iterable<ObjectType> thisInterfaces = getCtorImplementedInterfaces();
for (ObjectType thisInterface : thisInterfaces) {
if (thisInterface.isSubtype(that)) {
return true;
}
}
}
if (getConstructor() != null && getConstructor().isInterface()) {
for (ObjectType thisInterface : getCtorExtendedInterfaces()) {
if (thisInterface.isSubtype(that)) {
return true;
}
}
}
// other prototype based objects
if (isUnknownType() || implicitPrototypeChainIsUnknown()) {
// If unsure, say 'yes', to avoid spurious warnings.
// TODO(user): resolve the prototype chain completely in all cases,
// to avoid guessing.
return true;
}
return this.isImplicitPrototype(thatObj);
}
private boolean implicitPrototypeChainIsUnknown() {
ObjectType p = getImplicitPrototype();
while (p != null) {
if (p.isUnknownType()) {
return true;
}
p = p.getImplicitPrototype();
}
return false;
}
@Override
public boolean hasCachedValues() {
return super.hasCachedValues();
}
/** Whether this is a built-in object. */
@Override
public boolean isNativeObjectType() {
return nativeType;
}
@Override
void setOwnerFunction(FunctionType type) {
Preconditions.checkState(ownerFunction == null || type == null);
ownerFunction = type;
}
@Override
public FunctionType getOwnerFunction() {
return ownerFunction;
}
@Override
public Iterable<ObjectType> getCtorImplementedInterfaces() {
return isFunctionPrototypeType()
? getOwnerFunction().getImplementedInterfaces()
: ImmutableList.<ObjectType>of();
}
@Override
public Iterable<ObjectType> getCtorExtendedInterfaces() {
return isFunctionPrototypeType()
? getOwnerFunction().getExtendedInterfaces()
: ImmutableList.<ObjectType>of();
}
@Override
JSType resolveInternal(ErrorReporter t, StaticScope<JSType> scope) {
setResolvedTypeInternal(this);
ObjectType implicitPrototype = getImplicitPrototype();
if (implicitPrototype != null) {
implicitPrototypeFallback =
(ObjectType) implicitPrototype.resolve(t, scope);
}
for (Property prop : properties.values()) {
prop.setType(safeResolve(prop.getType(), t, scope));
}
return this;
}
@Override
public void match
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>GlobalReferences = true;
/** The function information map */
private FunctionInformationMap functionInformationMap;
/** Debugging information */
private final StringBuilder debugLog = new StringBuilder();
/** Detects Google-specific coding conventions. */
CodingConvention defaultCodingConvention = new ClosureCodingConvention();
private JSTypeRegistry typeRegistry;
private Config parserConfig = null;
private ReverseAbstractInterpreter abstractInterpreter;
private TypeValidator typeValidator;
public PerformanceTracker tracker;
// The oldErrorReporter exists so we can get errors from the JSTypeRegistry.
private final com.google.javascript.rhino.ErrorReporter oldErrorReporter =
RhinoErrorReporter.forOldRhino(this);
// This error reporter gets the messages from the current Rhino parser.
private final ErrorReporter defaultErrorReporter =
RhinoErrorReporter.forNewRhino(this);
/** Error strings used for reporting JSErrors */
public static final DiagnosticType OPTIMIZE_LOOP_ERROR = DiagnosticType.error(
"JSC_OPTIMIZE_LOOP_ERROR",
"Exceeded max number of optimization iterations: {0}");
public static final DiagnosticType MOTION_ITERATIONS_ERROR =
DiagnosticType.error("JSC_OPTIMIZE_LOOP_ERROR",
"Exceeded max number of code motion iterations: {0}");
private static final long COMPILER_STACK_SIZE = 1048576L;
/**
* Logger for the whole com.google.javascript.jscomp domain -
* setting configuration for this logger affects all loggers
* in other classes within the compiler.
*/
private static final Logger logger =
Logger.getLogger("com.google.javascript.jscomp");
private final PrintStream outStream;
private GlobalVarReferenceMap globalRefMap = null;
private volatile double progress = 0.0;
/**
* Creates a Compiler that reports errors and warnings to its logger.
*/
public Compiler() {
this((PrintStream) null);
}
/**
* Creates n Compiler that reports errors and warnings to an output
* stream.
*/
public Compiler(PrintStream stream) {
addChangeHandler(recentChange);
outStream = stream;
}
/**
* Creates a Compiler that uses a custom error manager.
*/
public Compiler(ErrorManager errorManager) {
this();
setErrorManager(errorManager);
}
/**
* Sets the error manager.
*
* @param errorManager the error manager, it cannot be {@code null}
*/
public void setErrorManager(ErrorManager errorManager) {
Preconditions.checkNotNull(
errorManager, "the error manager cannot be null");
this.errorManager = errorManager;
}
/**
* Creates a message formatter instance corresponding to the value of
* {@link CompilerOptions}.
*/
private MessageFormatter createMessageFormatter() {
boolean colorize = options.shouldColorizeErrorOutput();
return options.errorFormat.toFormatter(this, colorize);
}
/**
*
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>getInputId().getIdName());
Node newRoot = ast.getAstRoot(this);
if (newRoot == null) {
return false;
}
Node oldRoot = oldInput.getAstRoot(this);
if (oldRoot != null) {
oldRoot.getParent().replaceChild(oldRoot, newRoot);
} else {
getRoot().getLastChild().addChildToBack(newRoot);
}
CompilerInput newInput = new CompilerInput(ast);
putCompilerInput(ast.getInputId(), newInput);
JSModule module = oldInput.getModule();
if (module != null) {
module.addAfter(newInput, oldInput);
module.remove(oldInput);
}
// Verify the input id is set properly.
Preconditions.checkState(
newInput.getInputId().equals(oldInput.getInputId()));
InputId inputIdOnAst = newInput.getAstRoot(this).getInputId();
Preconditions.checkState(newInput.getInputId().equals(inputIdOnAst));
inputs.remove(oldInput);
return true;
}
/**
* Add a new source input dynamically. Intended for incremental compilation.
* <p>
* If the new source input doesn't parse, it will not be added, and a false
* will be returned.
*
* @param ast the JS Source to add.
* @return true if the source was added successfully, false otherwise.
* @throws IllegalStateException if an input for this ast already exists.
*/
boolean addNewSourceAst(JsAst ast) {
CompilerInput oldInput = getInput(ast.getInputId());
if (oldInput != null) {
throw new IllegalStateException(
"Input already exists: " + ast.getInputId().getIdName());
}
Node newRoot = ast.getAstRoot(this);
if (newRoot == null) {
return false;
}
getRoot().getLastChild().addChildToBack(newRoot);
CompilerInput newInput = new CompilerInput(ast);
// TODO(tylerg): handle this for multiple modules at some point.
if (moduleGraph == null && !modules.isEmpty()) {
// singleton module
modules.get(0).add(newInput);
}
putCompilerInput(ast.getInputId(), newInput);
return true;
}
@Override
JSModuleGraph getModuleGraph() {
return moduleGraph;
}
/**
* Gets a module graph. This will always return a module graph, even
* in the degenerate case when there's only one module.
*/
JSModuleGraph getDegenerateModuleGraph() {
return moduleGraph == null ? new JSModuleGraph(modules) : moduleGraph;
}
@Override
public JSTypeRegistry getTypeRegistry() {
if (typeRegistry == null) {
typeRegistry = new JSTypeRegistry(oldErrorReporter, options.looseTypes);
}
return typeRegistry;
}
@Override
public Memo
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> {
// Annotate the nodes in the tree with information from the
// input file. This information is used to construct the SourceMap.
SourceInformationAnnotator sia =
new SourceInformationAnnotator(
input.getName(), options.devMode != DevMode.OFF);
NodeTraversal.traverse(this, n, sia);
}
jsRoot.addChildToBack(n);
}
if (hasErrors()) {
return null;
}
return externAndJsRoot;
} finally {
stopTracer(tracer, "parseInputs");
}
}
/**
* Hoists inputs with the @externs annotation into the externs list.
*/
private void hoistExterns(Node externsRoot) {
boolean staleInputs = false;
for (CompilerInput input : inputs) {
if (options.dependencyOptions.needsManagement() &&
options.closurePass) {
// If we're doing scanning dependency info anyway, use that
// information to skip sources that obviously aren't externs.
if (!input.getProvides().isEmpty() || !input.getRequires().isEmpty()) {
continue;
}
}
Node n = input.getAstRoot(this);
// Inputs can have a null AST on a parse error.
if (n == null) {
continue;
}
JSDocInfo info = n.getJSDocInfo();
if (info != null && info.isExterns()) {
// If the input file is explicitly marked as an externs file, then
// assume the programmer made a mistake and throw it into
// the externs pile anyways.
externsRoot.addChildToBack(n);
input.setIsExtern(true);
input.getModule().remove(input);
externs.add(input);
staleInputs = true;
}
}
if (staleInputs) {
repartitionInputs();
}
}
/**
* Hoists inputs with the @nocompiler annotation out of the inputs.
*/
private void hoistNoCompileFiles() {
boolean staleInputs = false;
for (CompilerInput input : inputs) {
Node n = input.getAstRoot(this);
// Inputs can have a null AST on a parse error.
if (n == null) {
continue;
}
JSDocInfo info = n.getJSDocInfo();
if (info != null && info.isNoCompile()) {
input.getModule().remove(input);
staleInputs = true;
}
}
if (staleInputs) {
repartitionInputs();
}
}
private void repartitionInputs() {
fillEmptyModules(modules);
rebuildInputsFromModules();
}
/**
* Transforms AMD and CJS modules to something closure compiler can
* process and creates JSModules and the corresponding dependency tree
* on the way.
*/
void processAMDAndCommonJSModules() {
Map<String, JSModule> modulesByName = Maps.newLinked
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>; i++) {
Node scriptNode = inputs.get(i).getAstRoot(Compiler.this);
if (scriptNode == null) {
throw new IllegalArgumentException(
"Bad module: " + module.getName());
}
toSource(cb, i, scriptNode);
}
return cb.toString();
}
});
}
/**
* Converts the parse tree for each input in a module back to JS code.
*/
public String[] toSourceArray(final JSModule module) {
return runInCompilerThread(new Callable<String[]>() {
@Override
public String[] call() throws Exception {
List<CompilerInput> inputs = module.getInputs();
int numInputs = inputs.size();
if (numInputs == 0) {
return new String[0];
}
String[] sources = new String[numInputs];
CodeBuilder cb = new CodeBuilder();
for (int i = 0; i < numInputs; i++) {
Node scriptNode = inputs.get(i).getAstRoot(Compiler.this);
if (scriptNode == null) {
throw new IllegalArgumentException(
"Bad module input: " + inputs.get(i).getName());
}
cb.reset();
toSource(cb, i, scriptNode);
sources[i] = cb.toString();
}
return sources;
}
});
}
/**
* Writes out JS code from a root node. If printing input delimiters, this
* method will attach a comment to the start of the text indicating which
* input the output derived from. If there were any preserve annotations
* within the root's source, they will also be printed in a block comment
* at the beginning of the output.
*/
public void toSource(final CodeBuilder cb,
final int inputSeqNum,
final Node root) {
runInCompilerThread(new Callable<Void>() {
@Override
public Void call() throws Exception {
if (options.printInputDelimiter) {
if ((cb.getLength() > 0) && !cb.endsWith("\n")) {
cb.append("\n"); // Make sure that the label starts on a new line
}
Preconditions.checkState(root.isScript());
String delimiter = options.inputDelimiter;
String inputName = root.getInputId().getIdName();
String sourceName = root.getSourceFileName();
Preconditions.checkState(sourceName != null);
Preconditions.checkState(!sourceName.isEmpty());
delimiter = delimiter
.replaceAll("%name%", Matcher.quoteReplacement(inputName))
.replaceAll("%num%", String.valueOf(inputSeqNum));
cb.append(delimiter)
.append("\n");
}
if (root.getJSDocInfo() != null &&
root.getJSDocInfo().getLicense() != null) {
cb.append("/*\n")
.append(root.getJSDocInfo().getLicense())
.append("*/\n");
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>);
return DotFormatter.toDot(jsRoot, cfa.getCfg());
} else {
return "";
}
}
@Override
public ErrorManager getErrorManager() {
if (options == null) {
initOptions(newCompilerOptions());
}
return errorManager;
}
@Override
List<CompilerInput> getInputsInOrder() {
return Collections.<CompilerInput>unmodifiableList(inputs);
}
/**
* Returns an unmodifiable view of the compiler inputs indexed by id.
*/
public Map<InputId, CompilerInput> getInputsById() {
return Collections.unmodifiableMap(inputsById);
}
/**
* Gets the externs in the order in which they are being processed.
*/
List<CompilerInput> getExternsInOrder() {
return Collections.<CompilerInput>unmodifiableList(externs);
}
/**
* Stores the internal compiler state just before optimization is performed.
* This can be saved and restored in order to efficiently optimize multiple
* different output targets without having to perform checking multiple times.
*
* NOTE: This does not include all parts of the compiler's internal state. In
* particular, SourceFiles and CompilerOptions are not recorded. In
* order to recreate a Compiler instance from scratch, you would need to
* call {@code init} with the same arguments as in the initial creation before
* restoring intermediate state.
*/
public static class IntermediateState implements Serializable {
private static final long serialVersionUID = 1L;
Node externsRoot;
private Node jsRoot;
private List<CompilerInput> externs;
private List<CompilerInput> inputs;
private List<JSModule> modules;
private PassConfig.State passConfigState;
private JSTypeRegistry typeRegistry;
private AbstractCompiler.LifeCycleStage lifeCycleStage;
private Map<String, Node> injectedLibraries;
private IntermediateState() {}
}
/**
* Returns the current internal state, excluding the input files and modules.
*/
public IntermediateState getState() {
IntermediateState state = new IntermediateState();
state.externsRoot = externsRoot;
state.jsRoot = jsRoot;
state.externs = externs;
state.inputs = inputs;
state.modules = modules;
state.passConfigState = getPassConfig().getIntermediateState();
state.typeRegistry = typeRegistry;
state.lifeCycleStage = getLifeCycleStage();
state.injectedLibraries = Maps.newLinkedHashMap(injectedLibraries);
return state;
}
/**
* Sets the internal state to the capture given. Note that this assumes that
* the input files are already set up.
*/
public void setState(IntermediateState state) {
externsRoot = state.externsRoot;
jsRoot = state.jsRoot;
externs = state.externs;
inputs = state.inputs;
modules = state.modules;
passes = createPassConfigInternal();
get
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>PassConfig().setIntermediateState(state.passConfigState);
typeRegistry = state.typeRegistry;
setLifeCycleStage(state.lifeCycleStage);
injectedLibraries.clear();
injectedLibraries.putAll(state.injectedLibraries);
}
@VisibleForTesting
List<CompilerInput> getInputsForTesting() {
return inputs;
}
@VisibleForTesting
List<CompilerInput> getExternsForTesting() {
return externs;
}
@Override
boolean hasRegExpGlobalReferences() {
return hasRegExpGlobalReferences;
}
@Override
void setHasRegExpGlobalReferences(boolean references) {
hasRegExpGlobalReferences = references;
}
@Override
void updateGlobalVarReferences(Map<Var, ReferenceCollection> refMapPatch,
Node collectionRoot) {
Preconditions.checkState(collectionRoot.isScript()
|| collectionRoot.isBlock());
if (globalRefMap == null) {
globalRefMap = new GlobalVarReferenceMap(getInputsInOrder(),
getExternsInOrder());
}
globalRefMap.updateGlobalVarReferences(refMapPatch, collectionRoot);
}
@Override
GlobalVarReferenceMap getGlobalVarReferences() {
return globalRefMap;
}
@Override
CompilerInput getSynthesizedExternsInput() {
if (synthesizedExternsInput == null) {
synthesizedExternsInput = newExternInput(SYNTHETIC_EXTERNS);
}
return synthesizedExternsInput;
}
@Override
public double getProgress() {
return progress;
}
@Override
void setProgress(double newProgress) {
if (newProgress > 1.0) {
progress = 1.0;
} else if (newProgress < 0.0) {
progress = 0.0;
} else {
progress = newProgress;
}
}
/**
* Replaces one file in a hot-swap mode. The given JsAst should be made
* from a new version of a file that already was present in the last compile
* call. If the file is new, this will silently ignored.
*
* @param ast the ast of the file that is being replaced
*/
public void replaceScript(JsAst ast) {
CompilerInput input = this.getInput(ast.getInputId());
if (!replaceIncrementalSourceAst(ast)) {
return;
}
Node originalRoot = input.getAstRoot(this);
processNewScript(ast, originalRoot);
}
/**
* Adds a new Script AST to the compile state. If a script for the same file
* already exists the script will not be added, instead a call to
* #replaceScript should be used.
*
* @param ast the ast of the new file
*/
public void addNewScript(JsAst ast) {
if (!addNewSourceAst(ast)) {
return;
}
Node emptyScript = new Node(Token
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>1;
* </code>
* This property may only hold number values, and an assignment to any
* other type of value is an error.
*
* INFERRED properties do not have an explicit type annotation. Rather,
* we try to find all the possible types that this property can hold.
* <code>
* Foo.prototype.bar = 1;
* </code>
* If the programmer assigns other types of values to this property,
* the property will take on the union of all these types.
*
* UNKNOWN properties are properties on the UNKNOWN type. The UNKNOWN
* type has all properties, but we do not know whether they are
* declared or inferred.
*
*/
public abstract class ObjectType extends JSType implements StaticScope<JSType> {
private boolean visited;
private JSDocInfo docInfo = null;
private boolean unknown = true;
ObjectType(JSTypeRegistry registry) {
super(registry);
}
@Override
public Node getRootNode() { return null; }
@Override
public ObjectType getParentScope() {
return getImplicitPrototype();
}
@Override
public abstract Property getSlot(String name);
@Override
public Property getOwnSlot(String name) {
if (hasOwnProperty(name)) {
return getSlot(name);
}
return null;
}
@Override
public ObjectType getTypeOfThis() {
return null;
}
/**
* Gets the declared default element type.
* @see ParameterizedType
*/
public JSType getParameterType() {
return null;
}
/**
* Gets the declared default index type.
* @see IndexedType
*/
public JSType getIndexType() {
return null;
}
/**
* Gets the docInfo for this type.
*/
@Override
public JSDocInfo getJSDocInfo() {
if (docInfo != null) {
return docInfo;
} else if (getImplicitPrototype() != null) {
return getImplicitPrototype().getJSDocInfo();
} else {
return super.getJSDocInfo();
}
}
/**
* Sets the docInfo for this type from the given
* {@link JSDocInfo}. The {@code JSDocInfo} may be {@code null}.
*/
public void setJSDocInfo(JSDocInfo info) {
docInfo = info;
}
/**
* Detects a cycle in the implicit prototype chain. This method accesses
* the {@link #getImplicitPrototype()} method and must therefore be
* invoked only after the object is sufficiently initialized to respond to
* calls to this method.<p>
*
* The method is not thread safe.<p>
*
* @return True iff an implicit prototype cycle was detected.
*/
final boolean detectImplicitPrototypeCycle() {
// detecting cycle
this.visited = true;
ObjectType p = getImplicitPrototype();
while (p != null) {
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> from this ObjectType.
*
* @param propertyName the property's name
* @return true if the property was removed successfully. False if the
* property did not exist, or could not be removed.
*/
public boolean removeProperty(String propertyName) {
return false;
}
/**
* Gets the node corresponding to the definition of the specified property.
* This could be the node corresponding to declaration of the property or the
* node corresponding to the first reference to this property, e.g.,
* "this.propertyName" in a constructor. Note this is mainly intended to be
* an estimate of where in the source code a property is defined. Sometime
* the returned node is not even part of the global AST but in the AST of the
* JsDoc that defines a type.
*
* @param propertyName the name of the property
* @return the {@code Node} corresponding to the property or null.
*/
public Node getPropertyNode(String propertyName) {
return null;
}
/**
* Gets the docInfo on the specified property on this type. This should not
* be done implemented recursively, as you generally need to know exactly on
* which type in the prototype chain the JSDocInfo exists.
*/
public JSDocInfo getOwnPropertyJSDocInfo(String propertyName) {
return null;
}
/**
* Sets the docInfo for the specified property from the
* {@link JSDocInfo} on its definition.
* @param info {@code JSDocInfo} for the property definition. May be
* {@code null}.
*/
public void setPropertyJSDocInfo(String propertyName, JSDocInfo info) {
// by default, do nothing
}
@Override
public JSType findPropertyType(String propertyName) {
return hasProperty(propertyName) ?
getPropertyType(propertyName) : null;
}
/**
* Gets the property type of the property whose name is given. If the
* underlying object does not have this property, the Unknown type is
* returned to indicate that no information is available on this property.
*
* @return the property's type or {@link UnknownType}. This method never
* returns {@code null}.
*/
public abstract JSType getPropertyType(String propertyName);
/**
* Checks whether the property whose name is given is present on the
* object.
*/
public abstract boolean hasProperty(String propertyName);
/**
* Checks whether the property whose name is given is present directly on
* the object. Returns false even if it is declared on a supertype.
*/
public boolean hasOwnProperty(String propertyName) {
return hasProperty(propertyName);
}
/** Returns the names of all the properties directly on this type. */
public Set<String> getOwnPropertyNames() {
return ImmutableSet.of();
}
/**
* Checks whether the property's type is inferred.
*/
public abstract boolean isPropertyTypeInferred(String propertyName);
/**
* Checks whether the
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>unknown;
}
/**
* Clear cached values. Should be called before making changes to a prototype
* that may have been changed since creation.
*/
public void clearCachedValues() {
unknown = true;
}
/** Whether this is a built-in object. */
public boolean isNativeObjectType() {
return false;
}
/**
* A null-safe version of JSType#toObjectType.
*/
public static ObjectType cast(JSType type) {
return type == null ? null : type.toObjectType();
}
@Override
public final boolean isFunctionPrototypeType() {
return getOwnerFunction() != null;
}
/** Gets the owner of this if it's a function prototype. */
public FunctionType getOwnerFunction() {
return null;
}
/** Sets the owner function. By default, does nothing. */
void setOwnerFunction(FunctionType type) {}
/**
* Gets the interfaces implemented by the ctor associated with this type.
* Intended to be overridden by subclasses.
*/
public Iterable<ObjectType> getCtorImplementedInterfaces() {
return ImmutableSet.of();
}
/**
* Gets the interfaces extended by the interface associated with this type.
* Intended to be overridden by subclasses.
*/
public Iterable<ObjectType> getCtorExtendedInterfaces() {
return ImmutableSet.of();
}
public static final class Property
implements Serializable, StaticSlot<JSType>, StaticReference<JSType> {
private static final long serialVersionUID = 1L;
/**
* Property's name.
*/
private final String name;
/**
* Property's type.
*/
private JSType type;
/**
* Whether the property's type is inferred.
*/
private final boolean inferred;
/**
* The node corresponding to this property, e.g., a GETPROP node that
* declares this property.
*/
private Node propertyNode;
/** The JSDocInfo for this property. */
private JSDocInfo docInfo = null;
Property(String name, JSType type, boolean inferred,
Node propertyNode) {
this.name = name;
this.type = type;
this.inferred = inferred;
this.propertyNode = propertyNode;
}
@Override
public String getName() {
return name;
}
@Override
public Node getNode() {
return propertyNode;
}
@Override
public StaticSourceFile getSourceFile() {
return propertyNode == null ? null : propertyNode.getStaticSourceFile();
}
@Override
public Property getSymbol() {
return this;
}
@Override
public Property getDeclaration() {
return propertyNode == null ? null : this;
}
@Override
public JSType getType() {
return type;
}
@Override
public boolean isTypeInferred() {
return inferred;
}
boolean isFromExterns() {
return propertyNode == null ? false : propertyNode.isFromExterns();
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> }
void setType(JSType type) {
this.type = type;
}
@Override public JSDocInfo getJSDocInfo() {
return this.docInfo;
}
void setJSDocInfo(JSDocInfo info) {
this.docInfo = info;
}
public void setNode(Node n) {
this.propertyNode = n;
}
}
}
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> group : groups) {
set.addAll(group.types);
}
this.name = name;
this.types = ImmutableSet.copyOf(set);
}
/**
* Returns whether the given error's type matches a type
* in this group.
*/
public boolean matches(JSError error) {
return matches(error.getType());
}
/**
* Returns whether the given type matches a type in this group.
*/
public boolean matches(DiagnosticType type) {
return types.contains(type);
}
/**
* Returns whether all of the types in the given group are in this group.
*/
boolean isSubGroup(DiagnosticGroup group) {
for (DiagnosticType type : group.types) {
if (!matches(type)) {
return false;
}
}
return true;
}
/**
* Returns an iterable over all the types in this group.
*/
public Iterable<DiagnosticType> getTypes() {
return types;
}
@Override
public String toString() {
return name == null ? super.toString() : "DiagnosticGroup<" + name + ">";
}
}
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> which this refers (which is unknown if unresolved). */
public JSType getReferencedType() {
return getReferencedTypeInternal();
}
@Override
public String getReferenceName() {
return reference;
}
@Override
String toStringHelper(boolean forAnnotations) {
return reference;
}
@Override
public boolean hasReferenceName() {
return true;
}
@Override
boolean isNamedType() {
return true;
}
@Override
public boolean isNominalType() {
return true;
}
/**
* Two named types are equivalent if they are the same {@code
* ObjectType} object. This is complicated by the fact that isEquivalent
* is sometimes called before we have a chance to resolve the type
* names.
*
* @return {@code true} iff {@code that} == {@code this} or {@code that}
* is a {@link NamedType} whose reference is the same as ours,
* or {@code that} is the type we reference.
*/
@Override
public boolean isEquivalentTo(JSType that) {
if (this == that) {
return true;
}
ObjectType objType = ObjectType.cast(that);
if (objType != null) {
return objType.isNominalType() &&
reference.equals(objType.getReferenceName());
}
return false;
}
@Override
public int hashCode() {
return reference.hashCode();
}
/**
* Resolve the referenced type within the enclosing scope.
*/
@Override
JSType resolveInternal(ErrorReporter t, StaticScope<JSType> enclosing) {
// TODO(user): Investigate whether it is really necessary to keep two
// different mechanisms for resolving named types, and if so, which order
// makes more sense. Now, resolution via registry is first in order to
// avoid triggering the warnings built into the resolution via properties.
boolean resolved = resolveViaRegistry(t, enclosing);
if (detectImplicitPrototypeCycle()) {
handleTypeCycle(t);
}
if (resolved) {
super.resolveInternal(t, enclosing);
finishPropertyContinuations();
return registry.isLastGeneration() ?
getReferencedType() : this;
}
resolveViaProperties(t, enclosing);
if (detectImplicitPrototypeCycle()) {
handleTypeCycle(t);
}
super.resolveInternal(t, enclosing);
if (isResolved()) {
finishPropertyContinuations();
}
return registry.isLastGeneration() ?
getReferencedType() : this;
}
/**
* Resolves a named type by looking it up in the registry.
* @return True if we resolved successfully.
*/
private boolean resolveViaRegistry(
ErrorReporter t, StaticScope<JSType> enclosing) {
JSType type = registry.getType(reference);
if (type != null) {
setReferencedAndResolvedType(type, t, enclosing);
return
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> true;
}
return false;
}
/**
* Resolves a named type by looking up its first component in the scope, and
* subsequent components as properties. The scope must have been fully
* parsed and a symbol table constructed.
*/
private void resolveViaProperties(ErrorReporter t,
StaticScope<JSType> enclosing) {
JSType value = lookupViaProperties(t, enclosing);
// last component of the chain
if (value != null && value.isFunctionType() &&
(value.isConstructor() || value.isInterface())) {
FunctionType functionType = value.toMaybeFunctionType();
setReferencedAndResolvedType(
functionType.getInstanceType(), t, enclosing);
} else if (value != null && value.isNoObjectType()) {
setReferencedAndResolvedType(
registry.getNativeFunctionType(
JSTypeNative.NO_OBJECT_TYPE).getInstanceType(), t, enclosing);
} else if (value instanceof EnumType) {
setReferencedAndResolvedType(
((EnumType) value).getElementsType(), t, enclosing);
} else {
// We've been running into issues where people forward-declare
// non-named types. (This is legitimate...our dependency management
// code doubles as our forward-declaration code.)
//
// So if the type does resolve to an actual value, but it's not named,
// then don't respect the forward declaration.
handleUnresolvedType(t, value == null || value.isUnknownType());
}
}
/**
* Resolves a type by looking up its first component in the scope, and
* subsequent components as properties. The scope must have been fully
* parsed and a symbol table constructed.
* @return The type of the symbol, or null if the type could not be found.
*/
private JSType lookupViaProperties( ErrorReporter t,
StaticScope<JSType> enclosing) {
String[] componentNames = reference.split("\\.", -1);
if (componentNames[0].length() == 0) {
return null;
}
StaticSlot<JSType> slot = enclosing.getSlot(componentNames[0]);
if (slot == null) {
return null;
}
// If the first component has a type of 'Unknown', then any type
// names using it should be regarded as silently 'Unknown' rather than be
// noisy about it.
JSType slotType = slot.getType();
if (slotType == null || slotType.isAllType() || slotType.isNoType()) {
return null;
}
JSType value = getTypedefType(t, slot, componentNames[0]);
if (value == null) {
return null;
}
// resolving component by component
for (int i = 1; i < componentNames.length; i++) {
ObjectType parentClass = ObjectType.cast(value);
if (parentClass == null) {
return null;
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>
}
if (componentNames[i].length() == 0) {
return null;
}
value = parentClass.getPropertyType(componentNames[i]);
}
return value;
}
private void setReferencedAndResolvedType(JSType type, ErrorReporter t,
StaticScope<JSType> enclosing) {
if (validator != null) {
validator.apply(type);
}
setReferencedType(type);
checkEnumElementCycle(t);
setResolvedTypeInternal(getReferencedType());
}
private void handleTypeCycle(ErrorReporter t) {
setReferencedType(
registry.getNativeObjectType(JSTypeNative.UNKNOWN_TYPE));
t.warning("Cycle detected in inheritance chain of type " + reference,
sourceName, lineno, charno);
setResolvedTypeInternal(getReferencedType());
}
private void checkEnumElementCycle(ErrorReporter t) {
JSType referencedType = getReferencedType();
if (referencedType instanceof EnumElementType &&
((EnumElementType) referencedType).getPrimitiveType() == this) {
handleTypeCycle(t);
}
}
// Warns about this type being unresolved iff it's not a forward-declared
// type name.
private void handleUnresolvedType(
ErrorReporter t, boolean ignoreForwardReferencedTypes) {
if (registry.isLastGeneration()) {
boolean isForwardDeclared =
ignoreForwardReferencedTypes &&
registry.isForwardDeclaredType(reference);
if (!isForwardDeclared && registry.isLastGeneration()) {
t.warning("Bad type annotation. Unknown type " + reference,
sourceName, lineno, charno);
} else {
setReferencedType(
registry.getNativeObjectType(
JSTypeNative.NO_RESOLVED_TYPE));
if (registry.isLastGeneration() && validator != null) {
validator.apply(getReferencedType());
}
}
setResolvedTypeInternal(getReferencedType());
} else {
setResolvedTypeInternal(this);
}
}
JSType getTypedefType(ErrorReporter t, StaticSlot<JSType> slot, String name) {
JSType type = slot.getType();
if (type != null) {
return type;
}
handleUnresolvedType(t, true);
return null;
}
@Override
public boolean setValidator(Predicate<JSType> validator) {
// If the type is already resolved, we can validate it now. If
// the type has not been resolved yet, we need to wait till its
// resolved before we can validate it.
if (this.isResolved()) {
return super.setValidator(validator);
} else {
this.validator = validator;
return true;
}
}
/** Store enough information to define a property at a later time. */
private static final class PropertyContinuation {
private final String propertyName;
private final JSType type;
private final boolean inferred;
private final Node propertyNode;
private PropertyContinuation(
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>/*
* Copyright 2006 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Preconditions;
import com.google.javascript.rhino.InputId;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
/**
* <p>The syntactic scope creator scans the parse tree to create a Scope object
* containing all the variable declarations in that scope.</p>
*
* <p>This implementation is not thread-safe.</p>
*
*/
class SyntacticScopeCreator implements ScopeCreator {
private final AbstractCompiler compiler;
private Scope scope;
private InputId inputId;
private final RedeclarationHandler redeclarationHandler;
// The arguments variable is special, in that it's declared in every local
// scope, but not explicitly declared.
private static final String ARGUMENTS = "arguments";
public static final DiagnosticType VAR_MULTIPLY_DECLARED_ERROR =
DiagnosticType.error(
"JSC_VAR_MULTIPLY_DECLARED_ERROR",
"Variable {0} first declared in {1}");
public static final DiagnosticType VAR_ARGUMENTS_SHADOWED_ERROR =
DiagnosticType.error(
"JSC_VAR_ARGUMENTS_SHADOWED_ERROR",
"Shadowing \"arguments\" is not allowed");
/**
* Creates a ScopeCreator.
*/
SyntacticScopeCreator(AbstractCompiler compiler) {
this.compiler = compiler;
this.redeclarationHandler = new DefaultRedeclarationHandler();
}
SyntacticScopeCreator(
AbstractCompiler compiler, RedeclarationHandler redeclarationHandler) {
this.compiler = compiler;
this.redeclarationHandler = redeclarationHandler;
}
@Override
public Scope createScope(Node n, Scope parent) {
inputId = null;
if (parent == null) {
scope = new Scope(n, compiler);
} else {
scope = new Scope(parent, n);
}
scanRoot(n, parent);
inputId = null;
Scope returnedScope = scope;
scope = null;
return returnedScope;
}
private void scanRoot(Node n, Scope parent) {
if (n.isFunction()) {
if
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> (inputId == null) {
inputId = NodeUtil.getInputId(n);
// TODO(johnlenz): inputId maybe null if the FUNCTION node is detached
// from the AST.
// Is it meaningful to build a scope for detached FUNCTION node?
}
final Node fnNameNode = n.getFirstChild();
final Node args = fnNameNode.getNext();
final Node body = args.getNext();
// Bleed the function name into the scope, if it hasn't
// been declared in the outer scope.
String fnName = fnNameNode.getString();
if (!fnName.isEmpty() && NodeUtil.isFunctionExpression(n)) {
declareVar(fnNameNode);
}
// Args: Declare function variables
Preconditions.checkState(args.isParamList());
for (Node a = args.getFirstChild(); a != null;
a = a.getNext()) {
Preconditions.checkState(a.isName());
declareVar(a);
}
// Body
scanVars(body, n);
} else {
// It's the global block
Preconditions.checkState(scope.getParent() == null);
scanVars(n, null);
}
}
/**
* Scans and gather variables declarations under a Node
*/
private void scanVars(Node n, Node parent) {
switch (n.getType()) {
case Token.VAR:
// Declare all variables. e.g. var x = 1, y, z;
for (Node child = n.getFirstChild();
child != null;) {
Node next = child.getNext();
declareVar(child);
child = next;
}
return;
case Token.FUNCTION:
if (NodeUtil.isFunctionExpression(n)) {
return;
}
String fnName = n.getFirstChild().getString();
if (fnName.isEmpty()) {
// This is invalid, but allow it so the checks can catch it.
return;
}
declareVar(n.getFirstChild());
return; // should not examine function's children
case Token.CATCH:
Preconditions.checkState(n.getChildCount() == 2);
Preconditions.checkState(n.getFirstChild().isName());
// the first child is the catch var and the third child
// is the code block
final Node var = n.getFirstChild();
final Node block = var.getNext();
declareVar(var);
scanVars(block, n);
return; // only one child to scan
case Token.SCRIPT:
inputId = n.getInputId();
Preconditions.checkNotNull(inputId);
break;
}
// Variables can only occur in statement-level nodes, so
// we only need to traverse children in a couple special cases.
if (NodeUtil.isControlStructure(n) || NodeUtil.isStatementBlock(n)) {
for (Node child = n.getFirstChild();
child != null;) {
Node next = child.getNext
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>();
scanVars(child, n);
child = next;
}
}
}
/**
* Interface for injectable duplicate handling.
*/
interface RedeclarationHandler {
void onRedeclaration(
Scope s, String name, Node n, CompilerInput input);
}
/**
* The default handler for duplicate declarations.
*/
private class DefaultRedeclarationHandler implements RedeclarationHandler {
@Override
public void onRedeclaration(
Scope s, String name, Node n, CompilerInput input) {
Node parent = n.getParent();
// Don't allow multiple variables to be declared at the top-level scope
if (scope.isGlobal()) {
Scope.Var origVar = scope.getVar(name);
Node origParent = origVar.getParentNode();
if (origParent.isCatch() &&
parent.isCatch()) {
// Okay, both are 'catch(x)' variables.
return;
}
boolean allowDupe = hasDuplicateDeclarationSuppression(n, origVar);
if (!allowDupe) {
compiler.report(
JSError.make(NodeUtil.getSourceName(n), n,
VAR_MULTIPLY_DECLARED_ERROR,
name,
(origVar.input != null
? origVar.input.getName()
: "??")));
}
} else if (name.equals(ARGUMENTS) && !NodeUtil.isVarDeclaration(n)) {
// Disallow shadowing "arguments" as we can't handle with our current
// scope modeling.
compiler.report(
JSError.make(NodeUtil.getSourceName(n), n,
VAR_ARGUMENTS_SHADOWED_ERROR));
}
}
}
/**
* Declares a variable.
*
* @param n The node corresponding to the variable name.
*/
private void declareVar(Node n) {
Preconditions.checkState(n.isName());
CompilerInput input = compiler.getInput(inputId);
String name = n.getString();
if (scope.isDeclared(name, false)
|| (scope.isLocal() && name.equals(ARGUMENTS))) {
redeclarationHandler.onRedeclaration(
scope, name, n, input);
} else {
scope.declare(name, n, null, input);
}
}
/**
* @param n The name node to check.
* @param origVar The associated Var.
* @return Whether duplicated declarations warnings should be suppressed
* for the given node.
*/
static boolean hasDuplicateDeclarationSuppression(Node n, Scope.Var origVar) {
Preconditions.checkState(n.isName());
Node parent = n.getParent();
Node origParent = origVar.getParentNode();
JSDocInfo info = n.getJSDocInfo();
if (info == null) {
info = parent.getJSDocInfo();
}
if (info != null && info.getSuppressions().contains
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>("duplicate")) {
return true;
}
info = origVar.nameNode.getJSDocInfo();
if (info == null) {
info = origParent.getJSDocInfo();
}
return (info != null && info.getSuppressions().contains("duplicate"));
}
/**
* Generates an untyped global scope from the root of AST of compiler (which
* includes externs).
*
* @param compiler The compiler for which the scope is generated.
* @return The new untyped global scope generated as a result of this call.
*/
static Scope generateUntypedTopScope(AbstractCompiler compiler) {
return new SyntacticScopeCreator(compiler).createScope(compiler.getRoot(),
null);
}
}
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> defined as the exception name in CATCH clause so it became a
* variable local not to our definition of scope.</li>
* <li>Exported variables as they can be needed after the script terminates.
* </li>
* <li>Names of named functions because in JavaScript, <i>function foo(){}</i>
* does not kill <i>foo</i> in the dataflow.</li>
*/
static void computeEscaped(final Scope jsScope, final Set<Var> escaped,
AbstractCompiler compiler) {
// TODO(user): Very good place to store this information somewhere.
AbstractPostOrderCallback finder = new AbstractPostOrderCallback() {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (jsScope == t.getScope() || !n.isName()
|| parent.isFunction()) {
return;
}
String name = n.getString();
Var var = t.getScope().getVar(name);
if (var != null && var.scope == jsScope) {
escaped.add(jsScope.getVar(name));
}
}
};
NodeTraversal t = new NodeTraversal(compiler, finder);
t.traverseAtScope(jsScope);
// 1: Remove the exception name in CATCH which technically isn't local to
// begin with.
for (Iterator<Var> i = jsScope.getVars(); i.hasNext();) {
Var var = i.next();
if (var.getParentNode().isCatch() ||
compiler.getCodingConvention().isExported(var.getName())) {
escaped.add(var);
}
}
}
}
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>Type.error(
"JSC_ILLEGAL_NAME",
"identifiers ending in '__' cannot be used in Caja");
static final DiagnosticType DUPLICATE_OBJECT_KEY = DiagnosticType.warning(
"JSC_DUPLICATE_OBJECT_KEY",
"object literals cannot contain duplicate keys in ES5 strict mode");
private final AbstractCompiler compiler;
private final boolean noVarCheck;
private final boolean noCajaChecks;
StrictModeCheck(AbstractCompiler compiler) {
this(compiler, false, false);
}
StrictModeCheck(
AbstractCompiler compiler, boolean noVarCheck, boolean noCajaChecks) {
this.compiler = compiler;
this.noVarCheck = noVarCheck;
this.noCajaChecks = noCajaChecks;
}
@Override public void process(Node externs, Node root) {
NodeTraversal.traverseRoots(
compiler, Lists.newArrayList(externs, root), this);
NodeTraversal.traverse(compiler, root, new NonExternChecks());
}
@Override public void visit(NodeTraversal t, Node n, Node parent) {
if (n.isName()) {
if (!isDeclaration(n)) {
checkNameUse(t, n);
}
} else if (n.isAssign()) {
checkAssignment(t, n);
} else if (n.isDelProp()) {
checkDelete(t, n);
} else if (n.isObjectLit()) {
checkObjectLiteral(t, n);
} else if (n.isLabel()) {
checkLabel(t, n);
}
}
/**
* Determines if the given name is a declaration, which can be a declaration
* of a variable, function, or argument.
*/
private static boolean isDeclaration(Node n) {
switch (n.getParent().getType()) {
case Token.VAR:
case Token.FUNCTION:
case Token.CATCH:
return true;
case Token.PARAM_LIST:
return n.getParent().getParent().isFunction();
default:
return false;
}
}
/** Checks that the given name is used legally. */
private void checkNameUse(NodeTraversal t, Node n) {
Var v = t.getScope().getVar(n.getString());
if (v == null) {
// In particular, this prevents creating a global variable by assigning
// to it without a declaration.
if (!noVarCheck) {
t.report(n, UNKNOWN_VARIABLE, n.getString());
}
}
if (!noCajaChecks) {
if ("eval".equals(n.getString())) {
t.report(n, EVAL_USE);
} else if (n.getString().endsWith("__")) {
t.report(n, ILLEGAL_NAME);
}
}
}
/** Checks that an assignment is not to the "arguments" object. */
private void checkAssignment(NodeTraversal t, Node n
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>
*
* @param name the enum's name
* @param elementsType the base type of the individual elements
*/
EnumType(JSTypeRegistry registry, String name, Node source,
JSType elementsType) {
super(registry, "enum{" + name + "}", null);
this.source = source;
this.elementsType = new EnumElementType(registry, elementsType, name);
}
/**
* Gets the source node or null if this is an unknown enum.
*/
public Node getSource() {
return source;
}
@Override
public EnumType toMaybeEnumType() {
return this;
}
@Override
public ObjectType getImplicitPrototype() {
return registry.getNativeObjectType(JSTypeNative.OBJECT_TYPE);
}
/**
* Gets the elements defined on this enum.
* @return the elements' names defined on this enum. The returned set is
* immutable.
*/
public Set<String> getElements() {
return Collections.unmodifiableSet(elements);
}
/**
* Defines a new element on this enum.
* @param name the name of the new element
* @param definingNode the {@code Node} that defines this new element
* @return true iff the new element is added successfully
*/
public boolean defineElement(String name, Node definingNode) {
elements.add(name);
return defineDeclaredProperty(name, elementsType, definingNode);
}
/**
* Gets the elements' type.
*/
public EnumElementType getElementsType() {
return elementsType;
}
@Override
public TernaryValue testForEquality(JSType that) {
TernaryValue result = super.testForEquality(that);
if (result != null) {
return result;
}
return this.isEquivalentTo(that) ? TRUE : FALSE;
}
@Override
public boolean isSubtype(JSType that) {
return that.isEquivalentTo(getNativeType(JSTypeNative.OBJECT_TYPE)) ||
that.isEquivalentTo(getNativeType(JSTypeNative.OBJECT_PROTOTYPE)) ||
JSType.isSubtypeHelper(this, that);
}
@Override
String toStringHelper(boolean forAnnotations) {
return forAnnotations ? "Object" : getReferenceName();
}
@Override
public String getDisplayName() {
return elementsType.getDisplayName();
}
@Override
public <T> T visit(Visitor<T> visitor) {
return visitor.caseObjectType(this);
}
@Override
public FunctionType getConstructor() {
return null;
}
@Override
public boolean matchesNumberContext() {
return false;
}
@Override
public boolean matchesStringContext() {
return true;
}
@Override
public boolean matchesObjectContext() {
return true;
}
@Override
JSType resolveInternal(ErrorReporter t, StaticScope<JSType> scope) {
elementsType = (EnumElementType)
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> elementsType.resolve(t, scope);
return super.resolveInternal(t, scope);
}
}
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> compiler;
this.needToCheckDuplications = needToCheckDuplications;
this.style = style;
this.idGenerator = idGenerator;
checkLevel = (style == JsMessage.Style.CLOSURE)
? CheckLevel.ERROR : CheckLevel.WARNING;
// TODO(anatol): add flag that decides whether to process UNNAMED messages.
// Some projects would not want such functionality (unnamed) as they don't
// use SOY templates.
}
@Override
public void process(Node externs, Node root) {
NodeTraversal.traverse(compiler, root, this);
for (Map.Entry<Node, String> msgNode : googMsgNodes.entrySet()) {
compiler.report(JSError.make(msgNode.getValue(), msgNode.getKey(),
checkLevel, MESSAGE_NODE_IS_ORPHANED));
}
}
@Override
public void visit(NodeTraversal traversal, Node node, Node parent) {
String messageKey;
boolean isVar;
Node msgNode, msgNodeParent;
switch (node.getType()) {
case Token.NAME:
// var MSG_HELLO = 'Message'
if ((parent != null) && (parent.isVar())) {
messageKey = node.getString();
isVar = true;
} else {
return;
}
msgNode = node.getFirstChild();
msgNodeParent = node;
break;
case Token.ASSIGN:
// somenamespace.someclass.MSG_HELLO = 'Message'
isVar = false;
Node getProp = node.getFirstChild();
if (!getProp.isGetProp()) {
return;
}
Node propNode = getProp.getLastChild();
messageKey = propNode.getString();
msgNode = node.getLastChild();
msgNodeParent = node;
break;
case Token.CALL:
// goog.getMsg()
if (MSG_FUNCTION_NAME.equals(node.getFirstChild().getQualifiedName())) {
googMsgNodes.put(node, traversal.getSourceName());
}
return;
default:
return;
}
// Is this a message name?
boolean isNewStyleMessage =
msgNode != null && msgNode.isCall();
if (!isMessageName(messageKey, isNewStyleMessage)) {
return;
}
if (msgNode == null) {
compiler.report(
traversal.makeError(node, MESSAGE_HAS_NO_VALUE, messageKey));
return;
}
// Just report a warning if a qualified messageKey that looks like a message
// (e.g. "a.b.MSG_X") doesn't use goog.getMsg().
if (isNewStyleMessage) {
googMsgNodes.remove(msgNode);
} else if (style != JsMessage.Style.LEGACY) {
compiler.report(traversal.makeError(node, checkLevel,
MESSAGE_NOT_INITIALIZED_USING
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> builder the message builder
* @param nameNode a NAME node for a JS message variable
* @param parentNode a VAR node, parent of {@code nameNode}
* @param grandParentNode the grandparent of {@code nameNode}. This node is
* only used to get meta data about the message that might be
* surrounding it (e.g. a message description). This argument may be
* null if the meta data is not needed.
* @throws MalformedException if {@code varNode} does not
* correspond to a valid JS message VAR node
*/
private void extractMessageFromVariable(
Builder builder, Node nameNode, Node parentNode,
@Nullable Node grandParentNode) throws MalformedException {
// Determine the message's value
Node valueNode = nameNode.getFirstChild();
switch (valueNode.getType()) {
case Token.STRING:
case Token.ADD:
maybeInitMetaDataFromJsDocOrHelpVar(builder, parentNode,
grandParentNode);
builder.appendStringPart(extractStringFromStringExprNode(valueNode));
break;
case Token.FUNCTION:
maybeInitMetaDataFromJsDocOrHelpVar(builder, parentNode,
grandParentNode);
extractFromFunctionNode(builder, valueNode);
break;
case Token.CALL:
maybeInitMetaDataFromJsDoc(builder, parentNode);
extractFromCallNode(builder, valueNode);
break;
default:
throw new MalformedException("Cannot parse value of message "
+ builder.getKey(), valueNode);
}
}
/**
* Creates a {@link JsMessage} for a JS message defined using an assignment to
* a qualified name (e.g <code>a.b.MSG_X = goog.getMsg(...);</code>).
*
* @param builder the message builder
* @param getPropNode a GETPROP node in a JS message assignment
* @param assignNode an ASSIGN node, parent of {@code getPropNode}.
* @throws MalformedException if {@code getPropNode} does not
* correspond to a valid JS message node
*/
private void extractMessageFromProperty(
Builder builder, Node getPropNode, Node assignNode)
throws MalformedException {
Node callNode = getPropNode.getNext();
maybeInitMetaDataFromJsDoc(builder, assignNode);
extractFromCallNode(builder, callNode);
}
/**
* Initializes the meta data in a JsMessage by examining the nodes just before
* and after a message VAR node.
*
* @param builder the message builder whose meta data will be initialized
* @param varNode the message VAR node
* @param parentOfVarNode {@code varNode}'s parent node
*/
private void maybeInitMetaDataFromJsDocOrHelpVar(
Builder builder, Node varNode, @Nullable Node parentOfVarNode)
throws MalformedException {
// First check description in @desc
if (maybeInitMetaDataFromJsDoc
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>(builder, varNode)) {
return;
}
// Check the preceding node for meta data
if ((parentOfVarNode != null) &&
maybeInitMetaDataFromHelpVar(builder,
parentOfVarNode.getChildBefore(varNode))) {
return;
}
// Check the subsequent node for meta data
maybeInitMetaDataFromHelpVar(builder, varNode.getNext());
}
/**
* Initializes the meta data in a JsMessage by examining a node just before or
* after a message VAR node.
*
* @param builder the message builder whose meta data will be initialized
* @param sibling a node adjacent to the message VAR node
* @return true iff message has corresponding description variable
*/
private boolean maybeInitMetaDataFromHelpVar(Builder builder,
@Nullable Node sibling) throws MalformedException {
if ((sibling != null) && (sibling.isVar())) {
Node nameNode = sibling.getFirstChild();
String name = nameNode.getString();
if (name.equals(builder.getKey() + DESC_SUFFIX)) {
Node valueNode = nameNode.getFirstChild();
String desc = extractStringFromStringExprNode(valueNode);
if (desc.startsWith(HIDDEN_DESC_PREFIX)) {
builder.setDesc(desc.substring(HIDDEN_DESC_PREFIX.length()).trim());
builder.setIsHidden(true);
} else {
builder.setDesc(desc);
}
return true;
}
}
return false;
}
/**
* Initializes the meta data in a message builder given a node that may
* contain JsDoc properties.
*
* @param builder the message builder whose meta data will be initialized
* @param node the node with the message's JSDoc properties
* @return true if message has JsDoc with valid description in @desc
* annotation
*/
private boolean maybeInitMetaDataFromJsDoc(Builder builder, Node node) {
boolean messageHasDesc = false;
JSDocInfo info = node.getJSDocInfo();
if (info != null) {
String desc = info.getDescription();
if (desc != null) {
builder.setDesc(desc);
messageHasDesc = true;
}
if (info.isHidden()) {
builder.setIsHidden(true);
}
if (info.getMeaning() != null) {
builder.setMeaning(info.getMeaning());
}
}
return messageHasDesc;
}
/**
* Returns the string value associated with a node representing a JS string or
* several JS strings added together (e.g. {@code 'str'} or {@code 's' + 't' +
* 'r'}).
*
* @param node the node from where we extract the string
* @return String representation of the node
* @throws MalformedException if the parsed message is invalid
*/
private static String extractStringFromStringExprNode(Node node)
throws MalformedException {
switch
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> (node.getType()) {
case Token.STRING:
return node.getString();
case Token.ADD:
StringBuilder sb = new StringBuilder();
for (Node child : node.children()) {
sb.append(extractStringFromStringExprNode(child));
}
return sb.toString();
default:
throw new MalformedException("STRING or ADD node expected; found: " +
getReadableTokenName(node), node);
}
}
/**
* Initializes a message builder from a FUNCTION node.
* <p>
* <pre>
* The tree should look something like:
*
* function
* |-- name
* |-- lp
* | |-- name <arg1>
* | -- name <arg2>
* -- block
* |
* --return
* |
* --add
* |-- string foo
* -- name <arg1>
* </pre>
*
* @param builder the message builder
* @param node the function node that contains a message
* @throws MalformedException if the parsed message is invalid
*/
private void extractFromFunctionNode(Builder builder, Node node)
throws MalformedException {
Set<String> phNames = Sets.newHashSet();
for (Node fnChild : node.children()) {
switch (fnChild.getType()) {
case Token.NAME:
// This is okay. The function has a name, but it is empty.
break;
case Token.PARAM_LIST:
// Parse the placeholder names from the function argument list.
for (Node argumentNode : fnChild.children()) {
if (argumentNode.isName()) {
String phName = argumentNode.getString();
if (phNames.contains(phName)) {
throw new MalformedException("Duplicate placeholder name: "
+ phName, argumentNode);
} else {
phNames.add(phName);
}
}
}
break;
case Token.BLOCK:
// Build the message's value by examining the return statement
Node returnNode = fnChild.getFirstChild();
if (!returnNode.isReturn()) {
throw new MalformedException("RETURN node expected; found: "
+ getReadableTokenName(returnNode), returnNode);
}
for (Node child : returnNode.children()) {
extractFromReturnDescendant(builder, child);
}
// Check that all placeholders from the message text have appropriate
// object literal keys
for (String phName : builder.getPlaceholders()) {
if(!phNames.contains(phName)) {
throw new MalformedException(
"Unrecognized message placeholder referenced: " + phName,
returnNode);
}
}
break;
default:
throw new MalformedException(
"NAME, LP, or BLOCK node expected; found: "
+ getReadableTokenName(node), fnChild);
}
}
}
/**
* Appends value parts to
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> the message builder by traversing the descendants
* of the given RETURN node.
*
* @param builder the message builder
* @param node the node from where we extract a message
* @throws MalformedException if the parsed message is invalid
*/
private void extractFromReturnDescendant(Builder builder, Node node)
throws MalformedException {
switch (node.getType()) {
case Token.STRING:
builder.appendStringPart(node.getString());
break;
case Token.NAME:
builder.appendPlaceholderReference(node.getString());
break;
case Token.ADD:
for (Node child : node.children()) {
extractFromReturnDescendant(builder, child);
}
break;
default:
throw new MalformedException(
"STRING, NAME, or ADD node expected; found: "
+ getReadableTokenName(node), node);
}
}
/**
* Initializes a message builder from a CALL node.
* <p>
* The tree should look something like:
*
* <pre>
* call
* |-- getprop
* | |-- name 'goog'
* | +-- string 'getMsg'
* |
* |-- string 'Hi {$userName}! Welcome to {$product}.'
* +-- objlit
* |-- string 'userName'
* |-- name 'someUserName'
* |-- string 'product'
* +-- call
* +-- name 'getProductName'
* </pre>
*
* @param builder the message builder
* @param node the call node from where we extract the message
* @throws MalformedException if the parsed message is invalid
*/
private void extractFromCallNode(Builder builder,
Node node) throws MalformedException {
// Check the function being called
if (!node.isCall()) {
throw new MalformedException(
"Message must be initialized using " + MSG_FUNCTION_NAME +
" function.", node);
}
Node fnNameNode = node.getFirstChild();
if (!MSG_FUNCTION_NAME.equals(fnNameNode.getQualifiedName())) {
throw new MalformedException(
"Message initialized using unrecognized function. " +
"Please use " + MSG_FUNCTION_NAME + "() instead.", fnNameNode);
}
// Get the message string
Node stringLiteralNode = fnNameNode.getNext();
if (stringLiteralNode == null) {
throw new MalformedException("Message string literal expected",
stringLiteralNode);
}
// Parse the message string and append parts to the builder
parseMessageTextNode(builder, stringLiteralNode);
Node objLitNode = stringLiteralNode.getNext();
Set<String> phNames = Sets.newHashSet();
if (objLitNode != null) {
// Register the placeholder names
if (!objLitNode.isObjectLit()) {
throw new MalformedException("OBJLIT node expected", objLitNode);
}
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> = value.substring(nextPos);
} else {
// The message is parsed
return;
}
}
}
}
/**
* Processes found JS message. Several examples of "standard" processing
* routines are:
* <ol>
* <li>extract all JS messages
* <li>replace JS messages with localized versions for some specific language
* <li>check that messages have correct syntax and present in localization
* bundle
* </ol>
*
* @param message the found message
* @param definition the definition of the object and usually contains all
* additional message information like message node/parent's node
*/
abstract void processJsMessage(JsMessage message,
JsMessageDefinition definition);
/**
* Returns whether the given JS identifier is a valid JS message name.
*/
boolean isMessageName(String identifier, boolean isNewStyleMessage) {
return identifier.startsWith(MSG_PREFIX) &&
(style == JsMessage.Style.CLOSURE || isNewStyleMessage ||
!identifier.endsWith(DESC_SUFFIX));
}
/**
* Returns whether the given message name is in the unnamed namespace.
*/
private static boolean isUnnamedMessageName(String identifier) {
return MSG_UNNAMED_PATTERN.matcher(identifier).matches();
}
/**
* Returns whether a string is nonempty, begins with a lowercase letter, and
* contains only digits and underscores after the first underscore.
*/
static boolean isLowerCamelCaseWithNumericSuffixes(String input) {
return CAMELCASE_PATTERN.matcher(input).matches();
}
/**
* Returns human-readable name of the given node's type.
*/
private static String getReadableTokenName(Node node) {
return Token.name(node.getType());
}
/**
* Converts the given string from upper-underscore case to lower-camel case,
* preserving numeric suffixes. For example: "NAME" -> "name" "A4_LETTER" ->
* "a4Letter" "START_SPAN_1_23" -> "startSpan_1_23".
*/
static String toLowerCamelCaseWithNumericSuffixes(String input) {
// Determine where the numeric suffixes begin
int suffixStart = input.length();
while (suffixStart > 0) {
char ch = '\0';
int numberStart = suffixStart;
while (numberStart > 0) {
ch = input.charAt(numberStart - 1);
if (Character.isDigit(ch)) {
numberStart--;
} else {
break;
}
}
if ((numberStart > 0) && (numberStart < suffixStart) && (ch == '_')) {
suffixStart = numberStart - 1;
} else {
break;
}
}
if (suffixStart == input.length()) {
return CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_C
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> = LinkedHashMultimap.create();
} else {
this.invalidationMap = null;
}
}
@Override
public void process(Node externs, Node root) {
for (TypeMismatch mis : compiler.getTypeValidator().getMismatches()) {
addInvalidatingType(mis.typeA, mis.src);
addInvalidatingType(mis.typeB, mis.src);
}
StaticScope<T> scope = typeSystem.getRootScope();
NodeTraversal.traverse(compiler, externs, new FindExternProperties());
NodeTraversal.traverse(compiler, root, new FindRenameableProperties());
renameProperties();
}
private void recordInvalidationError(JSType t, JSError error) {
if (!t.isObject()) {
return;
}
if (invalidationMap != null) {
invalidationMap.put(t, error);
}
}
/**
* Invalidates the given type, so that no properties on it will be renamed.
*/
private void addInvalidatingType(JSType type, JSError error) {
type = type.restrictByNotNullOrUndefined();
if (type.isUnionType()) {
for (JSType alt : type.toMaybeUnionType().getAlternates()) {
addInvalidatingType(alt, error);
}
} else if (type.isEnumElementType()) {
addInvalidatingType(
type.toMaybeEnumElementType().getPrimitiveType(), error);
} else {
typeSystem.addInvalidatingType(type);
recordInvalidationError(type, error);
ObjectType objType = ObjectType.cast(type);
if (objType != null && objType.getImplicitPrototype() != null) {
typeSystem.addInvalidatingType(objType.getImplicitPrototype());
recordInvalidationError(objType.getImplicitPrototype(), error);
}
}
}
/** Returns the property for the given name, creating it if necessary. */
protected Property getProperty(String name) {
if (!properties.containsKey(name)) {
properties.put(name, new Property(name));
}
return properties.get(name);
}
/** Public for testing. */
T getTypeWithProperty(String field, T type) {
return typeSystem.getTypeWithProperty(field, type);
}
/** Tracks the current type system scope while traversing. */
private abstract class AbstractScopingCallback implements ScopedCallback {
protected final Stack<StaticScope<T>> scopes =
new Stack<StaticScope<T>>();
@Override
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
return true;
}
@Override
public void enterScope(NodeTraversal t) {
if (t.inGlobalScope()) {
scopes.push(typeSystem.getRootScope());
} else {
scopes.push(typeSystem.getFunctionScope(t.getScopeRoot()));
}
}
@Override
public void exitScope(
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>NodeTraversal t) {
scopes.pop();
}
/** Returns the current scope at this point in the file. */
protected StaticScope<T> getScope() {
return scopes.peek();
}
}
/**
* Finds all properties defined in the externs file and sets them as
* ineligible for renaming from the type on which they are defined.
*/
private class FindExternProperties extends AbstractScopingCallback {
@Override public void visit(NodeTraversal t, Node n, Node parent) {
// TODO(johnlenz): Support object-literal property definitions.
if (n.isGetProp()) {
String field = n.getLastChild().getString();
T type = typeSystem.getType(getScope(), n.getFirstChild(), field);
Property prop = getProperty(field);
if (typeSystem.isInvalidatingType(type)) {
prop.invalidate();
} else {
prop.addTypeToSkip(type);
// If this is a prototype property, then we want to skip assignments
// to the instance type as well. These assignments are not usually
// seen in the extern code itself, so we must handle them here.
if ((type = typeSystem.getInstanceFromPrototype(type)) != null) {
prop.getTypes().add(type);
prop.typesToSkip.add(type);
}
}
}
}
}
/**
* Traverses the tree, building a map from field names to Nodes for all
* fields that can be renamed.
*/
private class FindRenameableProperties extends AbstractScopingCallback {
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (n.isGetProp()) {
handleGetProp(t, n);
} else if (n.isObjectLit()) {
handleObjectLit(t, n);
}
}
/**
* Processes a GETPROP node.
*/
private void handleGetProp(NodeTraversal t, Node n) {
String name = n.getLastChild().getString();
T type = typeSystem.getType(getScope(), n.getFirstChild(), name);
Property prop = getProperty(name);
if (!prop.scheduleRenaming(n.getLastChild(),
processProperty(t, prop, type, null))) {
if (propertiesToErrorFor.containsKey(name)) {
String suggestion = "";
if (type instanceof JSType) {
JSType jsType = (JSType) type;
String qName = n.getFirstChild().getQualifiedName();
if (jsType.isAllType() || jsType.isUnknownType()) {
if (n.getFirstChild().isThis()) {
suggestion = "The \"this\" object is unknown in the function,"+
"consider using @this";
} else {
suggestion = "Consider casting " + qName +
" if you know it's type.";
}
} else {
List<String> errors = Lists.newArrayList();
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> printErrorLocations(errors, jsType);
if (!errors.isEmpty()) {
suggestion = "Consider fixing errors for the following types:\n";
suggestion += Joiner.on("\n").join(errors);
}
}
}
compiler.report(JSError.make(
t.getSourceName(), n, propertiesToErrorFor.get(name),
Warnings.INVALIDATION, name,
(type == null ? "null" : type.toString()),
n.toString(), suggestion));
}
}
}
/**
* Processes a OBJECTLIT node.
*/
private void handleObjectLit(NodeTraversal t, Node n) {
Node child = n.getFirstChild();
while (child != null) {
// Maybe STRING, GET, SET
// We should never see a mix of numbers and strings.
String name = child.getString();
T type = typeSystem.getType(getScope(), n, name);
Property prop = getProperty(name);
if (!prop.scheduleRenaming(child,
processProperty(t, prop, type, null))) {
// TODO(user): It doesn't look like the user can do much in this
// case right now.
if (propertiesToErrorFor.containsKey(name)) {
compiler.report(JSError.make(
t.getSourceName(), child, propertiesToErrorFor.get(name),
Warnings.INVALIDATION, name,
(type == null ? "null" : type.toString()), n.toString(), ""));
}
}
child = child.getNext();
}
}
private void printErrorLocations(List<String> errors, JSType t) {
if (!t.isObject() || t.isAllType()) {
return;
}
if (t.isUnionType()) {
for (JSType alt : t.toMaybeUnionType().getAlternates()) {
printErrorLocations(errors, alt);
}
return;
}
for (JSError error : invalidationMap.get(t)) {
if (errors.size() > MAX_INVALDIATION_WARNINGS_PER_PROPERTY) {
return;
}
errors.add(
t.toString() + " at " + error.sourceName + ":" + error.lineNumber);
}
}
/**
* Processes a property, adding it to the list of properties to rename.
* @return a representative type for the property reference, which will be
* the highest type on the prototype chain of the provided type. In the
* case of a union type, it will be the highest type on the prototype
* chain of one of the members of the union.
*/
private T processProperty(
NodeTraversal t, Property prop, T type, T relatedType) {
type = typeSystem.restrictByNotNullOrUndefined(type);
if (prop.skipRenaming || typeSystem.isInvalidatingType(type)) {
return null;
}
Iterable<T>
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> {
newName = typeName.replaceAll("[^\\w$]", "_") + "$" + name;
}
for (T type : set) {
names.put(type, newName);
}
}
return names;
}
/** Returns a map from field name to types for which it will be renamed. */
Multimap<String, Collection<T>> getRenamedTypesForTesting() {
Multimap<String, Collection<T>> ret = HashMultimap.create();
for (Map.Entry<String, Property> entry: properties.entrySet()) {
Property prop = entry.getValue();
if (!prop.skipRenaming) {
for (Collection<T> c : prop.getTypes().allEquivalenceClasses()) {
if (!c.isEmpty() && !prop.typesToSkip.contains(c.iterator().next())) {
ret.put(entry.getKey(), c);
}
}
}
}
return ret;
}
/** Interface for providing the type information needed by this pass. */
private interface TypeSystem<T> {
// TODO(user): add a getUniqueName(T type) method that is guaranteed
// to be unique, performant and human-readable.
/** Returns the top-most scope used by the type system (if any). */
StaticScope<T> getRootScope();
/** Returns the new scope started at the given function node. */
StaticScope<T> getFunctionScope(Node node);
/**
* Returns the type of the given node.
* @param prop Only types with this property need to be returned. In general
* with type tightening, this will require no special processing, but in
* the case of an unknown JSType, we might need to add in the native
* types since we don't track them, but only if they have the given
* property.
*/
T getType(StaticScope<T> scope, Node node, String prop);
/**
* Returns true if a field reference on this type will invalidate all
* references to that field as candidates for renaming. This is true if the
* type is unknown or all-inclusive, as variables with such a type could be
* references to any object.
*/
boolean isInvalidatingType(T type);
/**
* Informs the given type system that a type is invalidating due to a type
* mismatch found during type checking.
*/
void addInvalidatingType(JSType type);
/**
* Returns a set of types that should be skipped given the given type.
* This is necessary for interfaces when using JSTypes, as all super
* interfaces must also be skipped.
*/
ImmutableSet<T> getTypesToSkipForType(T type);
/**
* Determines whether the given type is one whose properties should not be
* considered for renaming.
*/
boolean isTypeToSkip(T type);
/** Remove null and undefined from the options in the given type. */
T restrictByNotNullOrUndefined(T
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> type);
/**
* Returns the alternatives if this is a type that represents multiple
* types, and null if not. Union and interface types can correspond to
* multiple other types.
*/
Iterable<T> getTypeAlternatives(T type);
/**
* Returns the type in the chain from the given type that contains the given
* field or null if it is not found anywhere.
*/
T getTypeWithProperty(String field, T type);
/**
* Returns the type of the instance of which this is the prototype or null
* if this is not a function prototype.
*/
T getInstanceFromPrototype(T type);
/**
* Records that this property could be referenced from any interface that
* this type, or any type in its superclass chain, implements.
*/
void recordInterfaces(T type, T relatedType,
DisambiguateProperties<T>.Property p);
}
/** Implementation of TypeSystem using JSTypes. */
private static class JSTypeSystem implements TypeSystem<JSType> {
private final Set<JSType> invalidatingTypes;
private JSTypeRegistry registry;
public JSTypeSystem(AbstractCompiler compiler) {
registry = compiler.getTypeRegistry();
invalidatingTypes = Sets.newHashSet(
registry.getNativeType(JSTypeNative.ALL_TYPE),
registry.getNativeType(JSTypeNative.NO_OBJECT_TYPE),
registry.getNativeType(JSTypeNative.NO_TYPE),
registry.getNativeType(JSTypeNative.FUNCTION_PROTOTYPE),
registry.getNativeType(JSTypeNative.FUNCTION_INSTANCE_TYPE),
registry.getNativeType(JSTypeNative.OBJECT_PROTOTYPE),
registry.getNativeType(JSTypeNative.TOP_LEVEL_PROTOTYPE),
registry.getNativeType(JSTypeNative.UNKNOWN_TYPE));
}
@Override public void addInvalidatingType(JSType type) {
checkState(!type.isUnionType());
invalidatingTypes.add(type);
}
@Override public StaticScope<JSType> getRootScope() { return null; }
@Override public StaticScope<JSType> getFunctionScope(Node node) {
return null;
}
@Override public JSType getType(
StaticScope<JSType> scope, Node node, String prop) {
if (node.getJSType() == null) {
return registry.getNativeType(JSTypeNative.UNKNOWN_TYPE);
}
return node.getJSType();
}
@Override public boolean isInvalidatingType(JSType type) {
if (type == null || invalidatingTypes.contains(type) ||
type.isUnknownType() /* unresolved types */) {
return true;
}
ObjectType objType = ObjectType.cast(type);
return objType != null && !objType.hasReferenceName();
}
@Override public ImmutableSet<JSType> getTypesToSkipForType(JSType type) {
type = type.restrict
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> CodingConvention codingConvention;
private final Set<JSType> invalidatingTypes = Sets.newHashSet();
// An array of native types that are not tracked by type tightening, and
// thus need to be added in if an unknown type is encountered.
private static final JSTypeNative [] nativeTypes = new JSTypeNative[] {
JSTypeNative.BOOLEAN_OBJECT_TYPE,
JSTypeNative.NUMBER_OBJECT_TYPE,
JSTypeNative.STRING_OBJECT_TYPE
};
public ConcreteTypeSystem(TightenTypes tt, CodingConvention convention) {
this.tt = tt;
this.codingConvention = convention;
}
@Override public void addInvalidatingType(JSType type) {
checkState(!type.isUnionType());
invalidatingTypes.add(type);
}
@Override public StaticScope<ConcreteType> getRootScope() {
return tt.getTopScope();
}
@Override public StaticScope<ConcreteType> getFunctionScope(Node decl) {
ConcreteFunctionType func = tt.getConcreteFunction(decl);
return (func != null) ?
func.getScope() : (StaticScope<ConcreteType>) null;
}
@Override
public ConcreteType getType(
StaticScope<ConcreteType> scope, Node node, String prop) {
if (scope != null) {
ConcreteType c = tt.inferConcreteType(
(TightenTypes.ConcreteScope) scope, node);
return maybeAddAutoboxes(c, node, prop);
} else {
return null;
}
}
/**
* Add concrete types for autoboxing types if necessary. The concrete type
* system does not track native types, like string, so add them if they are
* present in the JSType for the node.
*/
private ConcreteType maybeAddAutoboxes(
ConcreteType cType, Node node, String prop) {
JSType jsType = node.getJSType();
if (jsType == null) {
return cType;
} else if (jsType.isUnknownType()) {
for (JSTypeNative nativeType : nativeTypes) {
ConcreteType concrete = tt.getConcreteInstance(
tt.getTypeRegistry().getNativeObjectType(nativeType));
if (concrete != null && !concrete.getPropertyType(prop).isNone()) {
cType = cType.unionWith(concrete);
}
}
return cType;
}
return maybeAddAutoboxes(cType, jsType, prop);
}
private ConcreteType maybeAddAutoboxes(
ConcreteType cType, JSType jsType, String prop) {
jsType = jsType.restrictByNotNullOrUndefined();
if (jsType.isUnionType()) {
for (JSType alt : jsType.toMaybeUnionType().getAlternates()) {
cType = maybeAddAutoboxes(cType, alt, prop);
}
return cType;
} else if (jsType.isEnumElementType()) {
return
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>/*
* Copyright 2008 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
/**
* Check for usage of 'with'.
*
*/
class ControlStructureCheck implements HotSwapCompilerPass {
private final AbstractCompiler compiler;
static final DiagnosticType USE_OF_WITH = DiagnosticType.warning(
"JSC_USE_OF_WITH",
"The use of the 'with' structure should be avoided.");
ControlStructureCheck(AbstractCompiler compiler) {
this.compiler = compiler;
}
@Override
public void process(Node externs, Node root) {
check(root);
}
@Override
public void hotSwapScript(Node scriptRoot, Node originalRoot) {
check(scriptRoot);
}
/**
* Reports errors for any invalid use of control structures.
*
* @param node Current node to check.
*/
private void check(Node node) {
switch (node.getType()) {
case Token.WITH:
JSDocInfo info = node.getJSDocInfo();
boolean allowWith =
info != null && info.getSuppressions().contains("with");
if (!allowWith) {
report(node, USE_OF_WITH);
}
break;
}
for (Node bChild = node.getFirstChild(); bChild != null;) {
Node next = bChild.getNext();
check(bChild);
bChild = next;
}
}
private void report(Node n, DiagnosticType error) {
compiler.report(JSError.make(n.getSourceFileName(), n, error));
}
}
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>
}
@Override
public boolean isSubtype(JSType that) {
return referencedType.isSubtype(that);
}
@Override
public FunctionType getOwnerFunction() {
return referencedObjType == null
? null : referencedObjType.getOwnerFunction();
}
@Override
public Iterable<ObjectType> getCtorImplementedInterfaces() {
return referencedObjType == null ? Collections.<ObjectType>emptyList() :
referencedObjType.getCtorImplementedInterfaces();
}
@Override
public boolean canAssignTo(JSType that) {
return referencedType.canAssignTo(that);
}
@Override
public boolean isEquivalentTo(JSType that) {
if (this == that) {
return true;
}
return referencedType.isEquivalentTo(that);
}
@Override
public int hashCode() {
return referencedType.hashCode();
}
@Override
String toStringHelper(boolean forAnnotations) {
return referencedType.toStringHelper(forAnnotations);
}
@Override
public ObjectType getImplicitPrototype() {
return referencedObjType == null ? null :
referencedObjType.getImplicitPrototype();
}
@Override
boolean defineProperty(String propertyName, JSType type,
boolean inferred, Node propertyNode) {
return referencedObjType == null ? true :
referencedObjType.defineProperty(
propertyName, type, inferred, propertyNode);
}
@Override
public boolean removeProperty(String name) {
return referencedObjType == null ? false :
referencedObjType.removeProperty(name);
}
@Override
public boolean isPropertyTypeDeclared(String propertyName) {
return referencedObjType == null ? false :
referencedObjType.isPropertyTypeDeclared(propertyName);
}
@Override
public Node getPropertyNode(String propertyName) {
return referencedObjType == null ? null :
referencedObjType.getPropertyNode(propertyName);
}
@Override
public boolean isPropertyTypeInferred(String propertyName) {
return referencedObjType == null ? false :
referencedObjType.isPropertyTypeInferred(propertyName);
}
@Override
public boolean isPropertyInExterns(String propertyName) {
return referencedObjType == null ? false :
referencedObjType.isPropertyInExterns(propertyName);
}
@Override
public int getPropertiesCount() {
return referencedObjType == null ? 0 :
referencedObjType.getPropertiesCount();
}
@Override
protected void collectPropertyNames(Set<String> props) {
if (referencedObjType != null) {
referencedObjType.collectPropertyNames(props);
}
}
@Override
public JSType findPropertyType(String propertyName) {
return referencedType.findPropertyType(propertyName);
}
@Override
public JSType getPropertyType(String propertyName) {
return referencedObjType == null ?
getNativeType(JSTypeNative.UNKNOWN_TYPE) :
referencedObjType.getPropertyType(propertyName);
}
@Override
public JSDocInfo getJSDocInfo() {
return referencedType.getJSDocInfo();
}
@Override
public void setJSD
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>ocInfo(JSDocInfo info) {
if (referencedObjType != null) {
referencedObjType.setJSDocInfo(info);
}
}
@Override
public JSDocInfo getOwnPropertyJSDocInfo(String propertyName) {
return referencedObjType == null ? null :
referencedObjType.getOwnPropertyJSDocInfo(propertyName);
}
@Override
public void setPropertyJSDocInfo(String propertyName, JSDocInfo info) {
if (referencedObjType != null) {
referencedObjType.setPropertyJSDocInfo(propertyName, info);
}
}
@Override
public boolean hasProperty(String propertyName) {
return referencedObjType == null ? false :
referencedObjType.hasProperty(propertyName);
}
@Override
public boolean hasOwnProperty(String propertyName) {
return referencedObjType == null ? false :
referencedObjType.hasOwnProperty(propertyName);
}
@Override
public Set<String> getOwnPropertyNames() {
return referencedObjType == null ? ImmutableSet.<String>of() :
referencedObjType.getOwnPropertyNames();
}
@Override
public FunctionType getConstructor() {
return referencedObjType == null ? null :
referencedObjType.getConstructor();
}
@Override
public JSType getParameterType() {
return referencedObjType == null ? null :
referencedObjType.getParameterType();
}
@Override
public JSType getIndexType() {
return referencedObjType == null ? null :
referencedObjType.getIndexType();
}
@Override
public <T> T visit(Visitor<T> visitor) {
return referencedType.visit(visitor);
}
@Override
JSType resolveInternal(ErrorReporter t, StaticScope<JSType> scope) {
setReferencedType(referencedType.resolve(t, scope));
return this;
}
@Override
public String toDebugHashCodeString() {
return "{proxy:" + referencedType.toDebugHashCodeString() + "}";
}
@Override
public ObjectType getTypeOfThis() {
if (referencedObjType != null) {
return referencedObjType.getTypeOfThis();
}
return super.getTypeOfThis();
}
@Override
public JSType collapseUnion() {
if (referencedType.isUnionType()) {
return referencedType.collapseUnion();
}
return this;
}
@Override
public void matchConstraint(ObjectType constraint) {
referencedType.matchConstraint(constraint);
}
@Override
public ParameterizedType toMaybeParameterizedType() {
return referencedType.toMaybeParameterizedType();
}
@Override
public TemplateType toMaybeTemplateType() {
return referencedType.toMaybeTemplateType();
}
@Override
public boolean hasAnyTemplateInternal() {
return referencedType.hasAnyTemplate();
}
}
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> {
currentClass = getClassOfMethod(n, parent);
}
methodDepth++;
}
}
@Override
public void exitScope(NodeTraversal t) {
if (!t.inGlobalScope()) {
Node n = t.getScopeRoot();
Node parent = n.getParent();
if (isDeprecatedFunction(n, parent)) {
deprecatedDepth--;
}
methodDepth--;
if (methodDepth == 0) {
currentClass = null;
}
}
}
/**
* Gets the type of the class that "owns" a method, or null if
* we know that its un-owned.
*/
private JSType getClassOfMethod(Node n, Node parent) {
if (parent.isAssign()) {
Node lValue = parent.getFirstChild();
if (NodeUtil.isGet(lValue)) {
// We have an assignment of the form "a.b = ...".
JSType lValueType = lValue.getJSType();
if (lValueType != null && lValueType.isNominalConstructor()) {
// If a.b is a constructor, then everything in this function
// belongs to the "a.b" type.
return (lValueType.toMaybeFunctionType()).getInstanceType();
} else {
// If a.b is not a constructor, then treat this as a method
// of whatever type is on "a".
return normalizeClassType(lValue.getFirstChild().getJSType());
}
} else {
// We have an assignment of the form "a = ...", so pull the
// type off the "a".
return normalizeClassType(lValue.getJSType());
}
} else if (NodeUtil.isFunctionDeclaration(n) ||
parent.isName()) {
return normalizeClassType(n.getJSType());
}
return null;
}
/**
* Normalize the type of a constructor, its instance, and its prototype
* all down to the same type (the instance type).
*/
private JSType normalizeClassType(JSType type) {
if (type == null || type.isUnknownType()) {
return type;
} else if (type.isNominalConstructor()) {
return (type.toMaybeFunctionType()).getInstanceType();
} else if (type.isFunctionPrototypeType()) {
FunctionType owner = ((ObjectType) type).getOwnerFunction();
if (owner.isConstructor()) {
return owner.getInstanceType();
}
}
return type;
}
@Override
public boolean shouldTraverse(NodeTraversal t, Node n, Node parent) {
return true;
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getType()) {
case Token.NAME:
checkNameDeprecation(t, n, parent);
checkNameVisibility(t, n, parent);
break;
case Token.GETPROP:
checkPropertyDeprecation(t, n, parent);
checkPropertyVisibility(
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>t, n, parent);
checkConstantProperty(t, n);
break;
case Token.NEW:
checkConstructorDeprecation(t, n, parent);
break;
}
}
/**
* Checks the given NEW node to ensure that access restrictions are obeyed.
*/
private void checkConstructorDeprecation(NodeTraversal t, Node n,
Node parent) {
JSType type = n.getJSType();
if (type != null) {
String deprecationInfo = getTypeDeprecationInfo(type);
if (deprecationInfo != null &&
shouldEmitDeprecationWarning(t, n, parent)) {
if (!deprecationInfo.isEmpty()) {
compiler.report(
t.makeError(n, DEPRECATED_CLASS_REASON,
type.toString(), deprecationInfo));
} else {
compiler.report(
t.makeError(n, DEPRECATED_CLASS, type.toString()));
}
}
}
}
/**
* Checks the given NAME node to ensure that access restrictions are obeyed.
*/
private void checkNameDeprecation(NodeTraversal t, Node n, Node parent) {
// Don't bother checking definitions or constructors.
if (parent.isFunction() || parent.isVar() ||
parent.isNew()) {
return;
}
Scope.Var var = t.getScope().getVar(n.getString());
JSDocInfo docInfo = var == null ? null : var.getJSDocInfo();
if (docInfo != null && docInfo.isDeprecated() &&
shouldEmitDeprecationWarning(t, n, parent)) {
if (docInfo.getDeprecationReason() != null) {
compiler.report(
t.makeError(n, DEPRECATED_NAME_REASON, n.getString(),
docInfo.getDeprecationReason()));
} else {
compiler.report(
t.makeError(n, DEPRECATED_NAME, n.getString()));
}
}
}
/**
* Checks the given GETPROP node to ensure that access restrictions are
* obeyed.
*/
private void checkPropertyDeprecation(NodeTraversal t, Node n, Node parent) {
// Don't bother checking constructors.
if (parent.isNew()) {
return;
}
ObjectType objectType =
ObjectType.cast(dereference(n.getFirstChild().getJSType()));
String propertyName = n.getLastChild().getString();
if (objectType != null) {
String deprecationInfo
= getPropertyDeprecationInfo(objectType, propertyName);
if (deprecationInfo != null &&
shouldEmitDeprecationWarning(t, n, parent)) {
if (!deprecationInfo.isEmpty()) {
compiler.report(
t.makeError(n, DEPRECATED_PROP_REASON, propertyName,
validator.getReadableJSTypeName(n.getFirstChild(), true),
deprecationInfo));
} else {
compiler.report(
t.makeError(n, DEPRECATED_PROP, propertyName,
validator.
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>getReadableJSTypeName(n.getFirstChild(), true)));
}
}
}
}
/**
* Determines whether the given name is visible in the current context.
* @param t The current traversal.
* @param name The name node.
*/
private void checkNameVisibility(NodeTraversal t, Node name, Node parent) {
Var var = t.getScope().getVar(name.getString());
if (var != null) {
JSDocInfo docInfo = var.getJSDocInfo();
if (docInfo != null) {
// If a name is private, make sure that we're in the same file.
Visibility visibility = docInfo.getVisibility();
if (visibility == Visibility.PRIVATE) {
StaticSourceFile varSrc = var.getSourceFile();
StaticSourceFile refSrc = name.getStaticSourceFile();
if (varSrc != null &&
refSrc != null &&
!varSrc.getName().equals(refSrc.getName())) {
if (docInfo.isConstructor() &&
isValidPrivateConstructorAccess(parent)) {
return;
}
compiler.report(
t.makeError(name, BAD_PRIVATE_GLOBAL_ACCESS,
name.getString(), varSrc.getName()));
}
}
}
}
}
/**
* Determines whether the given property with @const tag got reassigned
* @param t The current traversal.
* @param getprop The getprop node.
*/
private void checkConstantProperty(NodeTraversal t,
Node getprop) {
// Check whether the property is modified
Node parent = getprop.getParent();
boolean isDelete = parent.isDelProp();
if (!(NodeUtil.isAssignmentOp(parent) && parent.getFirstChild() == getprop)
&& !parent.isInc() && !parent.isDec()
&& !isDelete) {
return;
}
ObjectType objectType =
ObjectType.cast(dereference(getprop.getFirstChild().getJSType()));
String propertyName = getprop.getLastChild().getString();
boolean isConstant = isPropertyDeclaredConstant(objectType, propertyName);
// Check whether constant properties are reassigned
if (isConstant) {
if (isDelete) {
compiler.report(
t.makeError(getprop, CONST_PROPERTY_DELETED, propertyName));
return;
}
ObjectType oType = objectType;
while (oType != null) {
if (oType.hasReferenceName()) {
if (initializedConstantProperties.containsEntry(
oType.getReferenceName(), propertyName)) {
compiler.report(
t.makeError(getprop, CONST_PROPERTY_REASSIGNED_VALUE,
propertyName));
break;
}
}
oType = oType.getImplicitPrototype();
}
Preconditions.checkState(objectType.hasReferenceName());
initializedConstantProperties.put(objectType.getReferenceName(),
propertyName);
// Add the prototype when we're looking at an instance object
if (objectType.
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>isInstanceType()) {
ObjectType prototype = objectType.getImplicitPrototype();
if (prototype != null) {
if (prototype.hasProperty(propertyName)
&& prototype.hasReferenceName()) {
initializedConstantProperties.put(prototype.getReferenceName(),
propertyName);
}
}
}
}
}
/**
* Determines whether the given property is visible in the current context.
* @param t The current traversal.
* @param getprop The getprop node.
*/
private void checkPropertyVisibility(NodeTraversal t,
Node getprop, Node parent) {
ObjectType objectType =
ObjectType.cast(dereference(getprop.getFirstChild().getJSType()));
String propertyName = getprop.getLastChild().getString();
if (objectType != null) {
// Is this a normal property access, or are we trying to override
// an existing property?
boolean isOverride = parent.getJSDocInfo() != null &&
parent.isAssign() &&
parent.getFirstChild() == getprop;
// Find the lowest property defined on a class with visibility
// information.
if (isOverride) {
objectType = objectType.getImplicitPrototype();
}
JSDocInfo docInfo = null;
for (; objectType != null;
objectType = objectType.getImplicitPrototype()) {
docInfo = objectType.getOwnPropertyJSDocInfo(propertyName);
if (docInfo != null &&
docInfo.getVisibility() != Visibility.INHERITED) {
break;
}
}
if (objectType == null) {
// We couldn't find a visibility modifier; assume it's public.
return;
}
String referenceSource = getprop.getSourceFileName();
String definingSource = docInfo.getSourceName();
boolean sameInput = referenceSource != null
&& referenceSource.equals(definingSource);
Visibility visibility = docInfo.getVisibility();
JSType ownerType = normalizeClassType(objectType);
if (isOverride) {
// Check an ASSIGN statement that's trying to override a property
// on a superclass.
JSDocInfo overridingInfo = parent.getJSDocInfo();
Visibility overridingVisibility = overridingInfo == null ?
Visibility.INHERITED : overridingInfo.getVisibility();
// Check that (a) the property *can* be overridden, and
// (b) that the visibility of the override is the same as the
// visibility of the original property.
if (visibility == Visibility.PRIVATE && !sameInput) {
compiler.report(
t.makeError(getprop, PRIVATE_OVERRIDE,
objectType.toString()));
} else if (overridingVisibility != Visibility.INHERITED &&
overridingVisibility != visibility) {
compiler.report(
t.makeError(getprop, VISIBILITY_MISMATCH,
visibility.name(), objectType.toString(),
overridingVisibility.name()));
}
} else {
if (sameInput) {
// private access is always
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> currently OK to access deprecated names and
* properties.
*
* There are 3 exceptions when we're allowed to use a deprecated
* type or property:
* 1) When we're in a deprecated function.
* 2) When we're in a deprecated class.
* 3) When we're in a static method of a deprecated class.
*/
private boolean canAccessDeprecatedTypes(NodeTraversal t) {
Node scopeRoot = t.getScopeRoot();
Node scopeRootParent = scopeRoot.getParent();
return
// Case #1
(deprecatedDepth > 0) ||
// Case #2
(getTypeDeprecationInfo(t.getScope().getTypeOfThis()) != null) ||
// Case #3
(scopeRootParent != null && scopeRootParent.isAssign() &&
getTypeDeprecationInfo(
getClassOfMethod(scopeRoot, scopeRootParent)) != null);
}
/**
* Returns whether this is a function node annotated as deprecated.
*/
private static boolean isDeprecatedFunction(Node n, Node parent) {
if (n.isFunction()) {
JSType type = n.getJSType();
if (type != null) {
return getTypeDeprecationInfo(type) != null;
}
}
return false;
}
/**
* Returns the deprecation reason for the type if it is marked
* as being deprecated. Returns empty string if the type is deprecated
* but no reason was given. Returns null if the type is not deprecated.
*/
private static String getTypeDeprecationInfo(JSType type) {
if (type == null) {
return null;
}
JSDocInfo info = type.getJSDocInfo();
if (info != null && info.isDeprecated()) {
if (info.getDeprecationReason() != null) {
return info.getDeprecationReason();
}
return "";
}
ObjectType objType = ObjectType.cast(type);
if (objType != null) {
ObjectType implicitProto = objType.getImplicitPrototype();
if (implicitProto != null) {
return getTypeDeprecationInfo(implicitProto);
}
}
return null;
}
/**
* Returns if a property is declared constant.
*/
private static boolean isPropertyDeclaredConstant(
ObjectType objectType, String prop) {
for (;
// Only objects with reference names can have constant properties.
objectType != null && objectType.hasReferenceName();
objectType = objectType.getImplicitPrototype()) {
JSDocInfo docInfo = objectType.getOwnPropertyJSDocInfo(prop);
if (docInfo != null && docInfo.isConstant()) {
return true;
}
}
return false;
}
/**
* Returns the deprecation reason for the property if it is marked
* as being deprecated. Returns empty string if the property is deprecated
* but no reason was given. Returns null if the property is not deprecated.
*/
private static String getPropertyDeprecationInfo(
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>ObjectType type,
String prop) {
JSDocInfo info = type.getOwnPropertyJSDocInfo(prop);
if (info != null && info.isDeprecated()) {
if (info.getDeprecationReason() != null) {
return info.getDeprecationReason();
}
return "";
}
ObjectType implicitProto = type.getImplicitPrototype();
if (implicitProto != null) {
return getPropertyDeprecationInfo(implicitProto, prop);
}
return null;
}
/**
* Dereference a type, autoboxing it and filtering out null.
*/
private static JSType dereference(JSType type) {
return type == null ? null : type.dereference();
}
}
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>
* In many JS libraries, the function that produces inheritance also
* adds properties to the superclass and/or subclass.
*/
public void applySubclassRelationship(FunctionType parentCtor,
FunctionType childCtor, SubclassType type);
/**
* Function name for abstract methods. An abstract method can be assigned to
* an interface method instead of an function expression in order to avoid
* linter warnings produced by assigning a function without a return value
* where a return value is expected.
* @return function name.
*/
public String getAbstractMethodName();
/**
* Checks if the given method defines a singleton getter, and if it does,
* returns the name of the class with the singleton getter. By default, always
* returns null. Meant to be overridden by subclasses.
*
* @param callNode A CALL node.
*/
public String getSingletonGetterClassName(Node callNode);
/**
* In many JS libraries, the function that adds a singleton getter to a class
* adds properties to the class.
*/
public void applySingletonGetter(FunctionType functionType,
FunctionType getterType, ObjectType objectType);
/**
* @return Whether the function is inlinable by convention.
*/
public boolean isInlinableFunction(Node n);
/**
* @return the delegate relationship created by the call or null.
*/
public DelegateRelationship getDelegateRelationship(Node callNode);
/**
* In many JS libraries, the function that creates a delegate relationship
* also adds properties to the delegator and delegate base.
*/
public void applyDelegateRelationship(
ObjectType delegateSuperclass, ObjectType delegateBase,
ObjectType delegator, FunctionType delegateProxy,
FunctionType findDelegate);
/**
* @return the name of the delegate superclass.
*/
public String getDelegateSuperclassName();
/**
* Checks for function calls that set the calling conventions on delegate
* methods.
*/
public void checkForCallingConventionDefiningCalls(
Node n, Map<String, String> delegateCallingConventions);
/**
* Defines the delegate proxy prototype properties. Their types depend on
* properties of the delegate base methods.
*
* @param delegateProxyPrototypes List of delegate proxy prototypes.
*/
public void defineDelegateProxyPrototypeProperties(
JSTypeRegistry registry, StaticScope<JSType> scope,
List<ObjectType> delegateProxyPrototypes,
Map<String, String> delegateCallingConventions);
/**
* Gets the name of the global object.
*/
public String getGlobalObject();
/**
* A Bind instance or null.
*/
public Bind describeFunctionBind(Node n);
/**
* A Bind instance or null.
* @param useTypeInfo If we believe type information is reliable enough
* to use to figure out what the bind function is.
*/
public Bind describeFunctionBind(Node n, boolean useTypeInfo);
public static class Bind {
// The target of the bind action
final Node target;
// The node representing the "this" value,
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>
}
@Override
public TypePair getTypesUnderEquality(JSType that) {
UnionTypeBuilder thisRestricted = new UnionTypeBuilder(registry);
UnionTypeBuilder thatRestricted = new UnionTypeBuilder(registry);
for (JSType element : alternates) {
TypePair p = element.getTypesUnderEquality(that);
if (p.typeA != null) {
thisRestricted.addAlternate(p.typeA);
}
if (p.typeB != null) {
thatRestricted.addAlternate(p.typeB);
}
}
return new TypePair(
thisRestricted.build(),
thatRestricted.build());
}
@Override
public TypePair getTypesUnderInequality(JSType that) {
UnionTypeBuilder thisRestricted = new UnionTypeBuilder(registry);
UnionTypeBuilder thatRestricted = new UnionTypeBuilder(registry);
for (JSType element : alternates) {
TypePair p = element.getTypesUnderInequality(that);
if (p.typeA != null) {
thisRestricted.addAlternate(p.typeA);
}
if (p.typeB != null) {
thatRestricted.addAlternate(p.typeB);
}
}
return new TypePair(
thisRestricted.build(),
thatRestricted.build());
}
@Override
public TypePair getTypesUnderShallowInequality(JSType that) {
UnionTypeBuilder thisRestricted = new UnionTypeBuilder(registry);
UnionTypeBuilder thatRestricted = new UnionTypeBuilder(registry);
for (JSType element : alternates) {
TypePair p = element.getTypesUnderShallowInequality(that);
if (p.typeA != null) {
thisRestricted.addAlternate(p.typeA);
}
if (p.typeB != null) {
thatRestricted.addAlternate(p.typeB);
}
}
return new TypePair(
thisRestricted.build(),
thatRestricted.build());
}
@Override
public <T> T visit(Visitor<T> visitor) {
return visitor.caseUnionType(this);
}
@Override
JSType resolveInternal(ErrorReporter t, StaticScope<JSType> scope) {
setResolvedTypeInternal(this); // for circularly defined types.
boolean changed = false;
ImmutableList.Builder<JSType> resolvedTypes = ImmutableList.builder();
for (JSType alternate : alternates) {
JSType newAlternate = alternate.resolve(t, scope);
changed |= (alternate != newAlternate);
resolvedTypes.add(alternate);
}
if (changed) {
Collection<JSType> newAlternates = resolvedTypes.build();
Preconditions.checkState(
newAlternates.hashCode() == this.hashcode);
alternates = newAlternates;
}
return this;
}
@Override
public String toDebugHashCodeString() {
List<String> hashCodes = Lists.newArrayList();
for (JS
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> long serialVersionUID = 1L;
NoObjectType(JSTypeRegistry registry) {
super(registry, null, null,
registry.createArrowType(null, null),
null, null, true, true);
getInternalArrowType().returnType = this;
this.setInstanceType(this);
}
@Override
public boolean isSubtype(JSType that) {
if (JSType.isSubtypeHelper(this, that)) {
return true;
} else {
return that.isObject() && !that.isNoType() && !that.isNoResolvedType();
}
}
@Override
public FunctionType toMaybeFunctionType() {
return null;
}
@Override
public boolean isNoObjectType() {
return true;
}
@Override
public ObjectType getImplicitPrototype() {
return null;
}
@Override
public String getReferenceName() {
return null;
}
@Override
public boolean matchesNumberContext() {
return true;
}
@Override
public boolean matchesObjectContext() {
return true;
}
@Override
public boolean matchesStringContext() {
return true;
}
@Override
public boolean isEquivalentTo(JSType that) {
return this == that;
}
@Override
public int hashCode() {
return System.identityHashCode(this);
}
@Override
public int getPropertiesCount() {
// Should never be called, returning the biggest number to highlight the
// 'unifying' role of this type.
return Integer.MAX_VALUE;
}
@Override
public JSType getPropertyType(String propertyName) {
// Return the least type to be a proper subtype of all other objects.
return getNativeType(JSTypeNative.NO_TYPE);
}
@Override
public boolean hasProperty(String propertyName) {
// has all properties, since it is any object
return true;
}
@Override
boolean defineProperty(String propertyName, JSType type,
boolean inferred, Node propertyNode) {
// nothing, all properties are defined
return true;
}
@Override
public boolean removeProperty(String name) {
return false;
}
@Override
public JSDocInfo getOwnPropertyJSDocInfo(String propertyName) {
return null;
}
@Override
public void setPropertyJSDocInfo(String propertyName, JSDocInfo info) {
// Do nothing, specific properties do not have JSDocInfo.
}
@Override
public boolean isPropertyTypeInferred(String propertyName) {
return false;
}
@Override
public <T> T visit(Visitor<T> visitor) {
return visitor.caseNoObjectType();
}
@Override
String toStringHelper(boolean forAnnotations) {
return forAnnotations ? "?" : "NoObject";
}
@Override
public FunctionType getConstructor() {
return null;
}
@Override
JSType resolveInternal(ErrorReporter t, StaticScope<JSType> scope
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> }
void addParserWarning(String messageId, int lineno, int charno) {
errorReporter.warning(ScriptRuntime.getMessage0(messageId),
getSourceName(), lineno, null, charno);
}
void addTypeWarning(String messageId, String messageArg, int lineno,
int charno) {
errorReporter.warning(
"Bad type annotation. " +
ScriptRuntime.getMessage1(messageId, messageArg),
getSourceName(), lineno, null, charno);
}
void addTypeWarning(String messageId, int lineno, int charno) {
errorReporter.warning(
"Bad type annotation. " +
ScriptRuntime.getMessage0(messageId),
getSourceName(), lineno, null, charno);
}
}
// The DocInfo with the fileoverview tag for the whole file.
private JSDocInfo fileOverviewJSDocInfo = null;
private State state;
private final Map<String, Annotation> annotationNames;
private final Set<String> suppressionNames;
static private final Set<String> modifiesAnnotationKeywords =
ImmutableSet.<String>of("this", "arguments");
private Node.FileLevelJsDocBuilder fileLevelJsDocBuilder;
/**
* Sets the JsDocBuilder for the file-level (root) node of this parse. The
* parser uses the builder to append any preserve annotations it encounters
* in JsDoc comments.
*
* @param fileLevelJsDocBuilder
*/
void setFileLevelJsDocBuilder(
Node.FileLevelJsDocBuilder fileLevelJsDocBuilder) {
this.fileLevelJsDocBuilder = fileLevelJsDocBuilder;
}
/**
* Sets the file overview JSDocInfo, in order to warn about multiple uses of
* the @fileoverview tag in a file.
*/
void setFileOverviewJSDocInfo(JSDocInfo fileOverviewJSDocInfo) {
this.fileOverviewJSDocInfo = fileOverviewJSDocInfo;
}
private enum State {
SEARCHING_ANNOTATION,
SEARCHING_NEWLINE,
NEXT_IS_ANNOTATION
}
JsDocInfoParser(JsDocTokenStream stream,
Comment commentNode,
Node associatedNode,
Config config,
ErrorReporter errorReporter) {
this.stream = stream;
this.associatedNode = associatedNode;
// Sometimes this will be null in tests.
this.sourceFile = associatedNode == null
? null : associatedNode.getStaticSourceFile();
this.jsdocBuilder = new JSDocInfoBuilder(config.parseJsDocDocumentation);
if (commentNode != null) {
this.jsdocBuilder.recordOriginalCommentString(commentNode.getValue());
}
this.annotationNames = config.annotationNames;
this.suppressionNames = config.suppressionNames;
this.errorReporter = errorReporter;
this.templateNode = this.createTemplateNode();
}
private String getSourceName()
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>
if (match(JsDocToken.STAR)) {
next();
}
}
}
/**
* Determines whether the parser has been populated with docinfo with a
* fileoverview tag.
*/
private boolean hasParsedFileOverviewDocInfo() {
return jsdocBuilder.isPopulatedWithFileOverview();
}
boolean hasParsedJSDocInfo() {
return jsdocBuilder.isPopulated();
}
JSDocInfo retrieveAndResetParsedJSDocInfo() {
return jsdocBuilder.build(associatedNode);
}
/**
* Gets the fileoverview JSDocInfo, if any.
*/
JSDocInfo getFileOverviewJSDocInfo() {
return fileOverviewJSDocInfo;
}
/**
* Look ahead for a type annotation by advancing the character stream.
* Does not modify the token stream.
* This is kind of a hack, and is only necessary because we use the token
* stream to parse types, but need the underlying character stream to get
* JsDoc descriptions.
* @return Whether we found a type annotation.
*/
private boolean lookAheadForTypeAnnotation() {
boolean matchedLc = false;
int c;
while (true) {
c = stream.getChar();
if (c == ' ') {
continue;
} else if (c == '{') {
matchedLc = true;
break;
} else {
break;
}
}
stream.ungetChar(c);
return matchedLc;
}
}
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>/*
*
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Rhino code, released
* May 6, 1999.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1997-1999
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Bob Jervis
* Google Inc.
*
* Alternatively, the contents of this file may be used under the terms of
* the GNU General Public License Version 2 or later (the "GPL"), in which
* case the provisions of the GPL are applicable instead of those above. If
* you wish to allow use of your version of this file only under the terms of
* the GPL and not to allow others to use your version of this file under the
* MPL, indicate your decision by deleting the provisions above and replacing
* them with the notice and other provisions required by the GPL. If you do
* not delete the provisions above, a recipient may use your version of this
* file under either the MPL or the GPL.
*
* ***** END LICENSE BLOCK ***** */
package com.google.javascript.rhino.jstype;
import com.google.javascript.rhino.JSDocInfo;
/**
* The {@code StaticSlot} interface must be implemented by variables that can
* appear as members of a {@code StaticScope}.
*
* @param <T> The type of information stored about the slot
*/
public interface StaticSlot<T> {
/**
* Gets the name of the slot.
*/
String getName();
/**
* Returns the type information, if any, for this slot.
* @return The type or {@code null} if no type is declared for it.
*/
T getType();
/**
* Returns whether the type has been inferred (as opposed to declared).
*/
boolean isTypeInferred();
/** Gets the declaration of this symbol. May not exist. */
StaticReference<T> getDeclaration();
/** Gets the JSDoc for this slot. */
JSDocInfo getJSDocInfo();
}
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>SError at a given source location
*
* @param sourceName The source file name
* @param lineno Line number with source file, or -1 if unknown
* @param charno Column number within line, or -1 for whole line.
* @param type The DiagnosticType
* @param arguments Arguments to be incorporated into the message
*/
public static JSError make(String sourceName, int lineno, int charno,
CheckLevel level, DiagnosticType type, String... arguments) {
return new JSError(
sourceName, null, lineno, charno, type, level, arguments);
}
/**
* Creates a JSError from a file and Node position.
*
* @param sourceName The source file name
* @param n Determines the line and char position within the source file name
* @param type The DiagnosticType
* @param arguments Arguments to be incorporated into the message
*/
public static JSError make(String sourceName, Node n,
DiagnosticType type, String... arguments) {
return new JSError(sourceName, n, type, arguments);
}
/**
* Creates a JSError from a file and Node position.
*
* @param sourceName The source file name
* @param n Determines the line and char position within the source file name
* @param type The DiagnosticType
* @param arguments Arguments to be incorporated into the message
*/
public static JSError make(String sourceName, Node n, CheckLevel level,
DiagnosticType type, String... arguments) {
return new JSError(sourceName, n, n.getLineno(), n.getCharno(), type, level,
arguments);
}
//
// JSError constructors
//
/**
* Creates a JSError at a CheckLevel for a source file location.
* Private to avoid any entanglement with code outside of the compiler.
*/
private JSError(
String sourceName, @Nullable Node node, int lineno, int charno,
DiagnosticType type, CheckLevel level, String... arguments) {
this.type = type;
this.node = node;
this.description = type.format.format(arguments);
this.lineNumber = lineno;
this.charno = charno;
this.sourceName = sourceName;
this.defaultLevel = level == null ? type.level : level;
this.level = level == null ? type.level : level;
}
/**
* Creates a JSError for a source file location. Private to avoid
* any entanglement with code outside of the compiler.
*/
private JSError(String sourceName, @Nullable Node node,
DiagnosticType type, String... arguments) {
this(sourceName,
node,
(node != null) ? node.getLineno() : -1,
(node != null) ? node.getCharno() : -1,
type, null, arguments);
}
public DiagnosticType getType() {
return type
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>ino.jstype.Visitor;
/**
* Chainable reverse abstract interpreter providing basic functionality.
*
*/
public abstract class ChainableReverseAbstractInterpreter
implements ReverseAbstractInterpreter {
protected final CodingConvention convention;
final JSTypeRegistry typeRegistry;
private ChainableReverseAbstractInterpreter firstLink;
private ChainableReverseAbstractInterpreter nextLink;
/**
* Constructs an interpreter, which is the only link in a chain. Interpreters
* can be appended using {@link #append}.
*/
public ChainableReverseAbstractInterpreter(CodingConvention convention,
JSTypeRegistry typeRegistry) {
Preconditions.checkNotNull(convention);
this.convention = convention;
this.typeRegistry = typeRegistry;
firstLink = this;
nextLink = null;
}
/**
* Appends a link to {@code this}, returning the updated last link.
* <p>
* The pattern {@code new X().append(new Y())...append(new Z())} forms a
* chain starting with X, then Y, then ... Z.
* @param lastLink a chainable interpreter, with no next link
* @return the updated last link
*/
public ChainableReverseAbstractInterpreter append(
ChainableReverseAbstractInterpreter lastLink) {
Preconditions.checkArgument(lastLink.nextLink == null);
this.nextLink = lastLink;
lastLink.firstLink = this.firstLink;
return lastLink;
}
/**
* Gets the first link of this chain.
*/
public ChainableReverseAbstractInterpreter getFirst() {
return firstLink;
}
/**
* Calculates the preciser scope starting with the first link.
*/
protected FlowScope firstPreciserScopeKnowingConditionOutcome(Node condition,
FlowScope blindScope, boolean outcome) {
return firstLink.getPreciserScopeKnowingConditionOutcome(
condition, blindScope, outcome);
}
/**
* Delegates the calculation of the preciser scope to the next link.
* If there is no next link, returns the blind scope.
*/
protected FlowScope nextPreciserScopeKnowingConditionOutcome(Node condition,
FlowScope blindScope, boolean outcome) {
return nextLink != null ? nextLink.getPreciserScopeKnowingConditionOutcome(
condition, blindScope, outcome) : blindScope;
}
/**
* Returns the type of a node in the given scope if the node corresponds to a
* name whose type is capable of being refined.
* @return The current type of the node if it can be refined, null otherwise.
*/
protected JSType getTypeIfRefinable(Node node, FlowScope scope) {
switch (node.getType()) {
case Token.NAME:
StaticSlot<JSType> nameVar = scope.getSlot(node.getString());
if (nameVar != null) {
JSType nameVarType = nameVar.getType();
if (nameVarType == null) {
nameVarType = node.getJS
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>Type();
}
return nameVarType;
}
return null;
case Token.GETPROP:
String qualifiedName = node.getQualifiedName();
if (qualifiedName == null) {
return null;
}
StaticSlot<JSType> propVar = scope.getSlot(qualifiedName);
JSType propVarType = null;
if (propVar != null) {
propVarType = propVar.getType();
}
if (propVarType == null) {
propVarType = node.getJSType();
}
if (propVarType == null) {
propVarType = getNativeType(UNKNOWN_TYPE);
}
return propVarType;
}
return null;
}
/**
* Declares a refined type in {@code scope} for the name represented by
* {@code node}. It must be possible to refine the type of the given node in
* the given scope, as determined by {@link #getTypeIfRefinable}.
*/
protected void declareNameInScope(FlowScope scope, Node node, JSType type) {
switch (node.getType()) {
case Token.NAME:
scope.inferSlotType(node.getString(), type);
break;
case Token.GETPROP:
String qualifiedName = node.getQualifiedName();
Preconditions.checkNotNull(qualifiedName);
JSType origType = node.getJSType();
origType = origType == null ? getNativeType(UNKNOWN_TYPE) : origType;
scope.inferQualifiedSlot(node, qualifiedName, origType, type);
break;
case Token.THIS:
// "this" references aren't currently modeled in the CFG.
break;
default:
throw new IllegalArgumentException("Node cannot be refined. \n" +
node.toStringTree());
}
}
/**
* @see #getRestrictedWithoutUndefined(JSType)
*/
private final Visitor<JSType> restrictUndefinedVisitor =
new Visitor<JSType>() {
@Override
public JSType caseEnumElementType(EnumElementType enumElementType) {
JSType type = enumElementType.getPrimitiveType().visit(this);
if (type != null && enumElementType.getPrimitiveType().equals(type)) {
return enumElementType;
} else {
return type;
}
}
@Override
public JSType caseAllType() {
return typeRegistry.createUnionType(OBJECT_TYPE, NUMBER_TYPE,
STRING_TYPE, BOOLEAN_TYPE, NULL_TYPE);
}
@Override
public JSType caseNoObjectType() {
return getNativeType(NO_OBJECT_TYPE);
}
@Override
public JSType caseNoType() {
return getNativeType(NO_TYPE);
}
@Override
public JSType caseBooleanType() {
return getNativeType(BOOLEAN_TYPE);
}
@Override
public JSType caseFunctionType(FunctionType type) {
return type;
}
@Override
public JSType caseNull
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>Type() {
return getNativeType(NULL_TYPE);
}
@Override
public JSType caseNumberType() {
return getNativeType(NUMBER_TYPE);
}
@Override
public JSType caseObjectType(ObjectType type) {
return type;
}
@Override
public JSType caseStringType() {
return getNativeType(STRING_TYPE);
}
@Override
public JSType caseUnionType(UnionType type) {
return type.getRestrictedUnion(getNativeType(VOID_TYPE));
}
@Override
public JSType caseUnknownType() {
return getNativeType(UNKNOWN_TYPE);
}
@Override
public JSType caseVoidType() {
return null;
}
@Override
public JSType caseParameterizedType(ParameterizedType type) {
return caseObjectType(type);
}
@Override
public JSType caseTemplateType(TemplateType templateType) {
return caseObjectType(templateType);
}
};
/**
* @see #getRestrictedWithoutNull(JSType)
*/
private final Visitor<JSType> restrictNullVisitor =
new Visitor<JSType>() {
@Override
public JSType caseEnumElementType(EnumElementType enumElementType) {
JSType type = enumElementType.getPrimitiveType().visit(this);
if (type != null && enumElementType.getPrimitiveType().equals(type)) {
return enumElementType;
} else {
return type;
}
}
@Override
public JSType caseAllType() {
return typeRegistry.createUnionType(OBJECT_TYPE, NUMBER_TYPE,
STRING_TYPE, BOOLEAN_TYPE, VOID_TYPE);
}
@Override
public JSType caseNoObjectType() {
return getNativeType(NO_OBJECT_TYPE);
}
@Override
public JSType caseNoType() {
return getNativeType(NO_TYPE);
}
@Override
public JSType caseBooleanType() {
return getNativeType(BOOLEAN_TYPE);
}
@Override
public JSType caseFunctionType(FunctionType type) {
return type;
}
@Override
public JSType caseNullType() {
return null;
}
@Override
public JSType caseNumberType() {
return getNativeType(NUMBER_TYPE);
}
@Override
public JSType caseObjectType(ObjectType type) {
return type;
}
@Override
public JSType caseStringType() {
return getNativeType(STRING_TYPE);
}
@Override
public JSType caseUnionType(UnionType type) {
return type.getRestrictedUnion(getNativeType(NULL_TYPE));
}
@Override
public JSType caseUnknownType() {
return getNativeType(UNKNOWN_TYPE);
}
@Override
public JSType caseVoidType() {
return getNativeType(VOID_TYPE);
}
@Override
public JSType caseParameterizedType(ParameterizedType type) {
return caseObjectType(type);
}
@Override
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>></tr>
* <tr><td>{@code number}</td><td>"number"</td></tr>
* <tr><td>{@code string}</td><td>"string"</td></tr>
* <tr><td>{@code Object} (which doesn't implement [[Call]])</td>
* <td>"object"</td></tr>
* <tr><td>{@code Object} (which implements [[Call]])</td>
* <td>"function"</td></tr>
* </table>
* @param type the type to restrict
* @param value A value known to be equal or not equal to the result of the
* {@code typeof} operation
* @param resultEqualsValue {@code true} if the {@code typeOf} result is known
* to equal {@code value}; {@code false} if it is known <em>not</em> to
* equal {@code value}
* @return the restricted type or null if no version of the type matches the
* restriction
*/
JSType getRestrictedByTypeOfResult(JSType type, String value,
boolean resultEqualsValue) {
if (type == null) {
if (resultEqualsValue) {
JSType result = getNativeTypeForTypeOf(value);
return result == null ? getNativeType(UNKNOWN_TYPE) : result;
} else {
return null;
}
}
return type.visit(
new RestrictByOneTypeOfResultVisitor(value, resultEqualsValue));
}
JSType getNativeType(JSTypeNative typeId) {
return typeRegistry.getNativeType(typeId);
}
/**
* If we definitely know what a type is based on the typeof result,
* return it. Otherwise, return null.
*
* The typeof operation in JS is poorly defined, and this function works
* for both the native typeof and goog.typeOf. It should not be made public,
* because its semantics are informally defined, and would be wrong in
* the general case.
*/
private JSType getNativeTypeForTypeOf(String value) {
if (value.equals("number")) {
return getNativeType(NUMBER_TYPE);
} else if (value.equals("boolean")) {
return getNativeType(BOOLEAN_TYPE);
} else if (value.equals("string")) {
return getNativeType(STRING_TYPE);
} else if (value.equals("undefined")) {
return getNativeType(VOID_TYPE);
} else if (value.equals("function")) {
return getNativeType(U2U_CONSTRUCTOR_TYPE);
} else {
return null;
}
}
}
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>/*
* Copyright 2010 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.collect.Maps;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import java.util.Map;
/**
* Filters warnings based on in-code {@code @suppress} annotations.
* @author nicksantos@google.com (Nick Santos)
*/
class SuppressDocWarningsGuard extends WarningsGuard {
private static final long serialVersionUID = 1L;
/** Warnings guards for each suppressible warnings group, indexed by name. */
private final Map<String, DiagnosticGroupWarningsGuard> suppressors =
Maps.newHashMap();
/**
* The suppressible groups, indexed by name.
*/
SuppressDocWarningsGuard(Map<String, DiagnosticGroup> suppressibleGroups) {
for (Map.Entry<String, DiagnosticGroup> entry :
suppressibleGroups.entrySet()) {
suppressors.put(
entry.getKey(),
new DiagnosticGroupWarningsGuard(
entry.getValue(),
CheckLevel.OFF));
}
}
@Override
public CheckLevel level(JSError error) {
Node node = error.node;
if (node != null) {
for (Node current = node;
current != null;
current = current.getParent()) {
int type = current.getType();
JSDocInfo info = null;
// We only care about function annotations at the FUNCTION and SCRIPT
// level. Otherwise, the @suppress annotation has an implicit
// dependency on the exact structure of our AST, and that seems like
// a bad idea.
if (type == Token.FUNCTION) {
info = NodeUtil.getFunctionJSDocInfo(current);
} else if (type == Token.SCRIPT) {
info = current.getJSDocInfo();
} else if (type == Token.ASSIGN) {
Node rhs = current.getLastChild();
if (rhs.isFunction()) {
info = NodeUtil.getFunctionJSDocInfo(rhs);
}
}
if (info != null) {
for (String suppressor : info.getSuppressions()) {
WarningsGuard guard = suppressors.
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> T processName(Name nameNode);
abstract T processNewExpression(NewExpression exprNode);
abstract T processNumberLiteral(NumberLiteral literalNode);
abstract T processObjectLiteral(ObjectLiteral literalNode);
abstract T processObjectProperty(ObjectProperty propertyNode);
abstract T processParenthesizedExpression(ParenthesizedExpression exprNode);
abstract T processPropertyGet(PropertyGet getNode);
abstract T processRegExpLiteral(RegExpLiteral literalNode);
abstract T processReturnStatement(ReturnStatement statementNode);
abstract T processScope(Scope scopeNode);
abstract T processStringLiteral(StringLiteral literalNode);
abstract T processSwitchCase(SwitchCase caseNode);
abstract T processSwitchStatement(SwitchStatement statementNode);
abstract T processThrowStatement(ThrowStatement statementNode);
abstract T processTryStatement(TryStatement statementNode);
abstract T processUnaryExpression(UnaryExpression exprNode);
abstract T processVariableDeclaration(VariableDeclaration declarationNode);
abstract T processVariableInitializer(VariableInitializer initializerNode);
abstract T processWhileLoop(WhileLoop loopNode);
abstract T processWithStatement(WithStatement statementNode);
abstract T processIllegalToken(AstNode node);
public T process(AstNode node) {
switch (node.getType()) {
case Token.ADD:
case Token.AND:
case Token.BITAND:
case Token.BITOR:
case Token.BITXOR:
case Token.COMMA:
case Token.DIV:
case Token.EQ:
case Token.GE:
case Token.GT:
case Token.IN:
case Token.INSTANCEOF:
case Token.LE:
case Token.LSH:
case Token.LT:
case Token.MOD:
case Token.MUL:
case Token.NE:
case Token.OR:
case Token.RSH:
case Token.SHEQ:
case Token.SHNE:
case Token.SUB:
case Token.URSH:
return processInfixExpression((InfixExpression) node);
case Token.ARRAYLIT:
return processArrayLiteral((ArrayLiteral) node);
case Token.ASSIGN:
case Token.ASSIGN_ADD:
case Token.ASSIGN_BITAND:
case Token.ASSIGN_BITOR:
case Token.ASSIGN_BITXOR:
case Token.ASSIGN_DIV:
case Token.ASSIGN_LSH:
case Token.ASSIGN_MOD:
case Token.ASSIGN_MUL:
case Token.ASSIGN_RSH:
case Token.ASSIGN_SUB:
case Token.ASSIGN_URSH:
return processAssignment((Assignment) node);
case Token.BITNOT:
case Token.DEC:
case Token.DELPROP:
case Token.INC:
case Token.NEG:
case Token.NOT:
case Token.POS:
case Token.TYPEOF:
case Token.VOID:
return processUnaryExpression((UnaryExpression) node);
case Token.BLOCK:
if (node instanceof Block) {
return processBlock((Block) node);
} else if (node instanceof
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> Scope) {
return processScope((Scope) node);
} else {
throw new IllegalStateException("Unexpected node type. class: " +
node.getClass() +
" type: " +
Token.typeToName(node.getType()));
}
case Token.BREAK:
return processBreakStatement((BreakStatement) node);
case Token.CALL:
return processFunctionCall((FunctionCall) node);
case Token.CASE:
case Token.DEFAULT:
return processSwitchCase((SwitchCase) node);
case Token.CATCH:
return processCatchClause((CatchClause) node);
case Token.COLON:
return processObjectProperty((ObjectProperty) node);
case Token.CONTINUE:
return processContinueStatement((ContinueStatement) node);
case Token.DO:
return processDoLoop((DoLoop) node);
case Token.EMPTY:
return (node instanceof EmptyExpression) ?
processEmptyExpression((EmptyExpression) node) :
processEmptyStatement((EmptyStatement) node);
case Token.EXPR_RESULT:
case Token.EXPR_VOID:
if (node instanceof ExpressionStatement) {
return processExpressionStatement((ExpressionStatement) node);
} else if (node instanceof LabeledStatement) {
return processLabeledStatement((LabeledStatement) node);
} else {
throw new IllegalStateException("Unexpected node type. class: " +
node.getClass() +
" type: " +
Token.typeToName(node.getType()));
}
case Token.DEBUGGER:
case Token.FALSE:
case Token.NULL:
case Token.THIS:
case Token.TRUE:
return processKeywordLiteral((KeywordLiteral) node);
case Token.FOR:
if (node instanceof ForInLoop) {
return processForInLoop((ForInLoop) node);
} else if (node instanceof ForLoop) {
return processForLoop((ForLoop) node);
} else {
throw new IllegalStateException("Unexpected node type. class: " +
node.getClass() +
" type: " +
Token.typeToName(node.getType()));
}
case Token.FUNCTION:
return processFunctionNode((FunctionNode) node);
case Token.GETELEM:
return processElementGet((ElementGet) node);
case Token.GETPROP:
return processPropertyGet((PropertyGet) node);
case Token.HOOK:
return processConditionalExpression((ConditionalExpression) node);
case Token.IF:
return processIfStatement((IfStatement) node);
case Token.LABEL:
return processLabel((Label) node);
case Token.LP:
return processParenthesizedExpression((ParenthesizedExpression) node);
case Token.NAME:
return processName((Name) node);
case Token.NEW:
return processNewExpression((NewExpression) node);
case Token.NUMBER:
return processNumberLiteral((NumberLiteral) node);
case Token.OBJECTLIT:
return processObjectLiteral((ObjectLiteral) node);
case Token.REGEXP:
return
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> processRegExpLiteral((RegExpLiteral) node);
case Token.RETURN:
return processReturnStatement((ReturnStatement) node);
case Token.SCRIPT:
return processAstRoot((AstRoot) node);
case Token.STRING:
return processStringLiteral((StringLiteral) node);
case Token.SWITCH:
return processSwitchStatement((SwitchStatement) node);
case Token.THROW:
return processThrowStatement((ThrowStatement) node);
case Token.TRY:
return processTryStatement((TryStatement) node);
case Token.CONST:
case Token.VAR:
if (node instanceof VariableDeclaration) {
return processVariableDeclaration((VariableDeclaration) node);
} else if (node instanceof VariableInitializer) {
return processVariableInitializer((VariableInitializer) node);
} else {
throw new IllegalStateException("Unexpected node type. class: " +
node.getClass() +
" type: " +
Token.typeToName(node.getType()));
}
case Token.WHILE:
return processWhileLoop((WhileLoop) node);
case Token.WITH:
return processWithStatement((WithStatement) node);
}
return processIllegalToken(node);
}
}
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>.
* Note: unlike getBooleanValue this function does not return UNKNOWN
* for expressions with side-effects.
*/
static TernaryValue getImpureBooleanValue(Node n) {
switch (n.getType()) {
case Token.ASSIGN:
case Token.COMMA:
// For ASSIGN and COMMA the value is the value of the RHS.
return getImpureBooleanValue(n.getLastChild());
case Token.NOT:
TernaryValue value = getImpureBooleanValue(n.getLastChild());
return value.not();
case Token.AND: {
TernaryValue lhs = getImpureBooleanValue(n.getFirstChild());
TernaryValue rhs = getImpureBooleanValue(n.getLastChild());
return lhs.and(rhs);
}
case Token.OR: {
TernaryValue lhs = getImpureBooleanValue(n.getFirstChild());
TernaryValue rhs = getImpureBooleanValue(n.getLastChild());
return lhs.or(rhs);
}
case Token.HOOK: {
TernaryValue trueValue = getImpureBooleanValue(
n.getFirstChild().getNext());
TernaryValue falseValue = getImpureBooleanValue(n.getLastChild());
if (trueValue.equals(falseValue)) {
return trueValue;
} else {
return TernaryValue.UNKNOWN;
}
}
case Token.ARRAYLIT:
case Token.OBJECTLIT:
// ignoring side-effects
return TernaryValue.TRUE;
case Token.VOID:
return TernaryValue.FALSE;
default:
return getPureBooleanValue(n);
}
}
/**
* Gets the boolean value of a node that represents a literal. This method
* effectively emulates the <code>Boolean()</code> JavaScript cast function
* except it return UNKNOWN for known values with side-effects, use
* getExpressionBooleanValue if you don't care about side-effects.
*/
static TernaryValue getPureBooleanValue(Node n) {
switch (n.getType()) {
case Token.STRING:
return TernaryValue.forBoolean(n.getString().length() > 0);
case Token.NUMBER:
return TernaryValue.forBoolean(n.getDouble() != 0);
case Token.NOT:
return getPureBooleanValue(n.getLastChild()).not();
case Token.NULL:
case Token.FALSE:
return TernaryValue.FALSE;
case Token.VOID:
if (!mayHaveSideEffects(n.getFirstChild())) {
return TernaryValue.FALSE;
}
break;
case Token.NAME:
String name = n.getString();
if ("undefined".equals(name)
|| "NaN".equals(name)) {
// We assume here that programs don't change the value of the keyword
// undefined to something other than the value undefined.
return TernaryValue.FALSE;
} else if ("Infinity".equals(name)) {
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> return TernaryValue.TRUE;
}
break;
case Token.TRUE:
case Token.REGEXP:
return TernaryValue.TRUE;
case Token.ARRAYLIT:
case Token.OBJECTLIT:
if (!mayHaveSideEffects(n)) {
return TernaryValue.TRUE;
}
break;
}
return TernaryValue.UNKNOWN;
}
/**
* Gets the value of a node as a String, or null if it cannot be converted.
* When it returns a non-null String, this method effectively emulates the
* <code>String()</code> JavaScript cast function.
*/
static String getStringValue(Node n) {
// TODO(user): regex literals as well.
switch (n.getType()) {
case Token.STRING:
case Token.STRING_KEY:
return n.getString();
case Token.NAME:
String name = n.getString();
if ("undefined".equals(name)
|| "Infinity".equals(name)
|| "NaN".equals(name)) {
return name;
}
break;
case Token.NUMBER:
return getStringValue(n.getDouble());
case Token.FALSE:
return "false";
case Token.TRUE:
return "true";
case Token.NULL:
return "null";
case Token.VOID:
return "undefined";
case Token.NOT:
TernaryValue child = getPureBooleanValue(n.getFirstChild());
if (child != TernaryValue.UNKNOWN) {
return child.toBoolean(true) ? "false" : "true"; // reversed.
}
break;
case Token.ARRAYLIT:
return arrayToString(n);
case Token.OBJECTLIT:
return "[object Object]";
}
return null;
}
static String getStringValue(double value) {
long longValue = (long) value;
// Return "1" instead of "1.0"
if (longValue == value) {
return Long.toString(longValue);
} else {
return Double.toString(value);
}
}
/**
* When converting arrays to string using Array.prototype.toString or
* Array.prototype.join, the rules for conversion to String are different
* than converting each element individually. Specifically, "null" and
* "undefined" are converted to an empty string.
* @param n A node that is a member of an Array.
* @return The string representation.
*/
static String getArrayElementStringValue(Node n) {
return (NodeUtil.isNullOrUndefined(n) || n.isEmpty())
? "" : getStringValue(n);
}
static String arrayToString(Node literal) {
Node first = literal.getFirstChild();
StringBuilder result = new StringBuilder();
int nextSlot = 0;
int nextSkipSlot = 0;
for (Node n = first; n != null; n = n.getNext()) {
String childValue = getArrayElementStringValue(
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>n);
if (childValue == null) {
return null;
}
if (n != first) {
result.append(',');
}
result.append(childValue);
nextSlot++;
}
return result.toString();
}
/**
* Gets the value of a node as a Number, or null if it cannot be converted.
* When it returns a non-null Double, this method effectively emulates the
* <code>Number()</code> JavaScript cast function.
*/
static Double getNumberValue(Node n) {
switch (n.getType()) {
case Token.TRUE:
return 1.0;
case Token.FALSE:
case Token.NULL:
return 0.0;
case Token.NUMBER:
return n.getDouble();
case Token.VOID:
if (mayHaveSideEffects(n.getFirstChild())) {
return null;
} else {
return Double.NaN;
}
case Token.NAME:
// Check for known constants
String name = n.getString();
if (name.equals("undefined")) {
return Double.NaN;
}
if (name.equals("NaN")) {
return Double.NaN;
}
if (name.equals("Infinity")) {
return Double.POSITIVE_INFINITY;
}
return null;
case Token.NEG:
if (n.getChildCount() == 1 && n.getFirstChild().isName()
&& n.getFirstChild().getString().equals("Infinity")) {
return Double.NEGATIVE_INFINITY;
}
return null;
case Token.NOT:
TernaryValue child = getPureBooleanValue(n.getFirstChild());
if (child != TernaryValue.UNKNOWN) {
return child.toBoolean(true) ? 0.0 : 1.0; // reversed.
}
break;
case Token.STRING:
return getStringNumberValue(n.getString());
case Token.ARRAYLIT:
case Token.OBJECTLIT:
String value = getStringValue(n);
return value != null ? getStringNumberValue(value) : null;
}
return null;
}
static Double getStringNumberValue(String rawJsString) {
if (rawJsString.contains("\u000b")) {
// vertical tab is not always whitespace
return null;
}
String s = trimJsWhiteSpace(rawJsString);
// return ScriptRuntime.toNumber(s);
if (s.length() == 0) {
return 0.0;
}
if (s.length() > 2
&& s.charAt(0) == '0'
&& (s.charAt(1) == 'x' || s.charAt(1) == 'X')) {
// Attempt to convert hex numbers.
try {
return Double.valueOf(Integer.parseInt(s.substring(2), 16));
} catch (NumberFormatException e) {
return Double.NaN;
}
}
if (
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>s.length() > 3
&& (s.charAt(0) == '-' || s.charAt(0) == '+')
&& s.charAt(1) == '0'
&& (s.charAt(2) == 'x' || s.charAt(2) == 'X')) {
// hex numbers with explicit signs vary between browsers.
return null;
}
// Firefox and IE treat the "Infinity" differently. Firefox is case
// insensitive, but IE treats "infinity" as NaN. So leave it alone.
if (s.equals("infinity")
|| s.equals("-infinity")
|| s.equals("+infinity")) {
return null;
}
try {
return Double.parseDouble(s);
} catch (NumberFormatException e) {
return Double.NaN;
}
}
static String trimJsWhiteSpace(String s) {
int start = 0;
int end = s.length();
while (end > 0
&& isStrWhiteSpaceChar(s.charAt(end - 1)) == TernaryValue.TRUE) {
end--;
}
while (start < end
&& isStrWhiteSpaceChar(s.charAt(start)) == TernaryValue.TRUE) {
start++;
}
return s.substring(start, end);
}
/**
* Copied from Rhino's ScriptRuntime
*/
static TernaryValue isStrWhiteSpaceChar(int c) {
switch (c) {
case '\u000B': // <VT>
return TernaryValue.UNKNOWN; // IE says "no", ECMAScript says "yes"
case ' ': // <SP>
case '\n': // <LF>
case '\r': // <CR>
case '\t': // <TAB>
case '\u00A0': // <NBSP>
case '\u000C': // <FF>
case '\u2028': // <LS>
case '\u2029': // <PS>
case '\uFEFF': // <BOM>
return TernaryValue.TRUE;
default:
return (Character.getType(c) == Character.SPACE_SEPARATOR)
? TernaryValue.TRUE : TernaryValue.FALSE;
}
}
/**
* Gets the function's name. This method recognizes five forms:
* <ul>
* <li>{@code function name() ...}</li>
* <li>{@code var name = function() ...}</li>
* <li>{@code qualified.name = function() ...}</li>
* <li>{@code var name2 = function name1() ...}</li>
* <li>{@code qualified.name2 = function name1() ...}</li>
* </ul>
* In two last cases with named function expressions, the second name is
* returned (the variable of qualified name).
*
* @param n a node whose type is {@link Token#FUNCTION
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>}
* @return the function's name, or {@code null} if it has no name
*/
static String getFunctionName(Node n) {
Preconditions.checkState(n.isFunction());
Node parent = n.getParent();
switch (parent.getType()) {
case Token.NAME:
// var name = function() ...
// var name2 = function name1() ...
return parent.getQualifiedName();
case Token.ASSIGN:
// qualified.name = function() ...
// qualified.name2 = function name1() ...
return parent.getFirstChild().getQualifiedName();
default:
// function name() ...
String name = n.getFirstChild().getQualifiedName();
return name;
}
}
/**
* Gets the function's name. This method recognizes the forms:
* <ul>
* <li>{@code {'name': function() ...}}</li>
* <li>{@code {name: function() ...}}</li>
* <li>{@code function name() ...}</li>
* <li>{@code var name = function() ...}</li>
* <li>{@code qualified.name = function() ...}</li>
* <li>{@code var name2 = function name1() ...}</li>
* <li>{@code qualified.name2 = function name1() ...}</li>
* </ul>
*
* @param n a node whose type is {@link Token#FUNCTION}
* @return the function's name, or {@code null} if it has no name
*/
public static String getNearestFunctionName(Node n) {
if (!n.isFunction()) {
return null;
}
String name = getFunctionName(n);
if (name != null) {
return name;
}
// Check for the form { 'x' : function() { } }
Node parent = n.getParent();
switch (parent.getType()) {
case Token.SETTER_DEF:
case Token.GETTER_DEF:
case Token.STRING_KEY:
// Return the name of the literal's key.
return parent.getString();
case Token.NUMBER:
return getStringValue(parent);
}
return null;
}
/**
* Returns true if this is an immutable value.
*/
static boolean isImmutableValue(Node n) {
switch (n.getType()) {
case Token.STRING:
case Token.NUMBER:
case Token.NULL:
case Token.TRUE:
case Token.FALSE:
return true;
case Token.NOT:
return isImmutableValue(n.getFirstChild());
case Token.VOID:
case Token.NEG:
return isImmutableValue(n.getFirstChild());
case Token.NAME:
String name = n.getString();
// We assume here that programs don't change the value of the keyword
// undefined to something other than the value undefined.
return "undefined".equals(name
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>)
|| "Infinity".equals(name)
|| "NaN".equals(name);
}
return false;
}
/**
* Returns true if the operator on this node is symmetric
*/
public static boolean isSymmetricOperation(Node n) {
switch (n.getType()) {
case Token.EQ: // equal
case Token.NE: // not equal
case Token.SHEQ: // exactly equal
case Token.SHNE: // exactly not equal
case Token.MUL: // multiply, unlike add it only works on numbers
// or results NaN if any of the operators is not a number
return true;
}
return false;
}
/**
* Returns true if the operator on this node is relational.
* the returned set does not include the equalities.
*/
public static boolean isRelationalOperation(Node n) {
switch (n.getType()) {
case Token.GT: // equal
case Token.GE: // not equal
case Token.LT: // exactly equal
case Token.LE: // exactly not equal
return true;
}
return false;
}
/**
* Returns the inverse of an operator if it is invertible.
* ex. '>' ==> '<'
*/
public static int getInverseOperator(int type) {
switch (type) {
case Token.GT:
return Token.LT;
case Token.LT:
return Token.GT;
case Token.GE:
return Token.LE;
case Token.LE:
return Token.GE;
}
return Token.ERROR;
}
/**
* Returns true if this is a literal value. We define a literal value
* as any node that evaluates to the same thing regardless of when or
* where it is evaluated. So /xyz/ and [3, 5] are literals, but
* the name a is not.
*
* Function literals do not meet this definition, because they
* lexically capture variables. For example, if you have
* <code>
* function() { return a; }
* </code>
* If it is evaluated in a different scope, then it
* captures a different variable. Even if the function did not read
* any captured variables directly, it would still fail this definition,
* because it affects the lifecycle of variables in the enclosing scope.
*
* However, a function literal with respect to a particular scope is
* a literal.
*
* @param includeFunctions If true, all function expressions will be
* treated as literals.
*/
static boolean isLiteralValue(Node n, boolean includeFunctions) {
switch (n.getType()) {
case Token.ARRAYLIT:
for (Node child = n.getFirstChild(); child != null;
child = child.getNext()) {
if ((!child.isEmpty()) && !isLiteralValue(child, includeFunctions)) {
return false;
}
}
return true;
case Token.REGEXP:
// Return true only
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> if all children are const.
for (Node child = n.getFirstChild(); child != null;
child = child.getNext()) {
if (!isLiteralValue(child, includeFunctions)) {
return false;
}
}
return true;
case Token.OBJECTLIT:
// Return true only if all values are const.
for (Node child = n.getFirstChild(); child != null;
child = child.getNext()) {
if (!isLiteralValue(child.getFirstChild(), includeFunctions)) {
return false;
}
}
return true;
case Token.FUNCTION:
return includeFunctions && !NodeUtil.isFunctionDeclaration(n);
default:
return isImmutableValue(n);
}
}
/**
* Determines whether the given value may be assigned to a define.
*
* @param val The value being assigned.
* @param defines The list of names of existing defines.
*/
static boolean isValidDefineValue(Node val, Set<String> defines) {
switch (val.getType()) {
case Token.STRING:
case Token.NUMBER:
case Token.TRUE:
case Token.FALSE:
return true;
// Binary operators are only valid if both children are valid.
case Token.ADD:
case Token.BITAND:
case Token.BITNOT:
case Token.BITOR:
case Token.BITXOR:
case Token.DIV:
case Token.EQ:
case Token.GE:
case Token.GT:
case Token.LE:
case Token.LSH:
case Token.LT:
case Token.MOD:
case Token.MUL:
case Token.NE:
case Token.RSH:
case Token.SHEQ:
case Token.SHNE:
case Token.SUB:
case Token.URSH:
return isValidDefineValue(val.getFirstChild(), defines)
&& isValidDefineValue(val.getLastChild(), defines);
// Unary operators are valid if the child is valid.
case Token.NOT:
case Token.NEG:
case Token.POS:
return isValidDefineValue(val.getFirstChild(), defines);
// Names are valid if and only if they are defines themselves.
case Token.NAME:
case Token.GETPROP:
if (val.isQualifiedName()) {
return defines.contains(val.getQualifiedName());
}
}
return false;
}
/**
* Returns whether this a BLOCK node with no children.
*
* @param block The node.
*/
static boolean isEmptyBlock(Node block) {
if (!block.isBlock()) {
return false;
}
for (Node n = block.getFirstChild(); n != null; n = n.getNext()) {
if (!n.isEmpty()) {
return false;
}
}
return true;
}
static boolean isSimpleOperator(Node n) {
return isSimpleOperatorType(n.getType());
}
/**
* A "simple" operator is one
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> whose children are expressions,
* has no direct side-effects (unlike '+='), and has no
* conditional aspects (unlike '||').
*/
static boolean isSimpleOperatorType(int type) {
switch (type) {
case Token.ADD:
case Token.BITAND:
case Token.BITNOT:
case Token.BITOR:
case Token.BITXOR:
case Token.COMMA:
case Token.DIV:
case Token.EQ:
case Token.GE:
case Token.GETELEM:
case Token.GETPROP:
case Token.GT:
case Token.INSTANCEOF:
case Token.LE:
case Token.LSH:
case Token.LT:
case Token.MOD:
case Token.MUL:
case Token.NE:
case Token.NOT:
case Token.RSH:
case Token.SHEQ:
case Token.SHNE:
case Token.SUB:
case Token.TYPEOF:
case Token.VOID:
case Token.POS:
case Token.NEG:
case Token.URSH:
return true;
default:
return false;
}
}
/**
* Creates an EXPR_RESULT.
*
* @param child The expression itself.
* @return Newly created EXPR node with the child as subexpression.
*/
public static Node newExpr(Node child) {
return IR.exprResult(child).srcref(child);
}
/**
* Returns true if the node may create new mutable state, or change existing
* state.
*
* @see <a href="http://www.xkcd.org/326/">XKCD Cartoon</a>
*/
static boolean mayEffectMutableState(Node n) {
return mayEffectMutableState(n, null);
}
static boolean mayEffectMutableState(Node n, AbstractCompiler compiler) {
return checkForStateChangeHelper(n, true, compiler);
}
/**
* Returns true if the node which may have side effects when executed.
*/
static boolean mayHaveSideEffects(Node n) {
return mayHaveSideEffects(n, null);
}
static boolean mayHaveSideEffects(Node n, AbstractCompiler compiler) {
return checkForStateChangeHelper(n, false, compiler);
}
/**
* Returns true if some node in n's subtree changes application state.
* If {@code checkForNewObjects} is true, we assume that newly created
* mutable objects (like object literals) change state. Otherwise, we assume
* that they have no side effects.
*/
private static boolean checkForStateChangeHelper(
Node n, boolean checkForNewObjects, AbstractCompiler compiler) {
// Rather than id which ops may have side effects, id the ones
// that we know to be safe
switch (n.getType()) {
// other side-effect free statements and expressions
case Token.AND:
case Token.BLOCK:
case Token.EXPR
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>_RESULT:
case Token.HOOK:
case Token.IF:
case Token.IN:
case Token.PARAM_LIST:
case Token.NUMBER:
case Token.OR:
case Token.THIS:
case Token.TRUE:
case Token.FALSE:
case Token.NULL:
case Token.STRING:
case Token.STRING_KEY:
case Token.SWITCH:
case Token.TRY:
case Token.EMPTY:
break;
// Throws are by definition side effects
case Token.THROW:
return true;
case Token.OBJECTLIT:
if (checkForNewObjects) {
return true;
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (checkForStateChangeHelper(
c.getFirstChild(), checkForNewObjects, compiler)) {
return true;
}
}
return false;
case Token.ARRAYLIT:
case Token.REGEXP:
if (checkForNewObjects) {
return true;
}
break;
case Token.VAR: // empty var statement (no declaration)
case Token.NAME: // variable by itself
if (n.getFirstChild() != null) {
return true;
}
break;
case Token.FUNCTION:
// Function expressions don't have side-effects, but function
// declarations change the namespace. Either way, we don't need to
// check the children, since they aren't executed at declaration time.
return checkForNewObjects || !isFunctionExpression(n);
case Token.NEW:
if (checkForNewObjects) {
return true;
}
if (!constructorCallHasSideEffects(n)) {
// loop below will see if the constructor parameters have
// side-effects
break;
}
return true;
case Token.CALL:
// calls to functions that have no side effects have the no
// side effect property set.
if (!functionCallHasSideEffects(n, compiler)) {
// loop below will see if the function parameters have
// side-effects
break;
}
return true;
default:
if (isSimpleOperatorType(n.getType())) {
break;
}
if (isAssignmentOp(n)) {
Node assignTarget = n.getFirstChild();
if (assignTarget.isName()) {
return true;
}
// Assignments will have side effects if
// a) The RHS has side effects, or
// b) The LHS has side effects, or
// c) A name on the LHS will exist beyond the life of this statement.
if (checkForStateChangeHelper(
n.getFirstChild(), checkForNewObjects, compiler) ||
checkForStateChangeHelper(
n.getLastChild(), checkForNewObjects, compiler)) {
return true;
}
if (isGet(assignTarget)) {
// If the object being assigned to is a local object, don't
// consider this a side-effect as it can
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>'t be referenced
// elsewhere. Don't do this recursively as the property might
// be an alias of another object, unlike a literal below.
Node current = assignTarget.getFirstChild();
if (evaluatesToLocalValue(current)) {
return false;
}
// A literal value as defined by "isLiteralValue" is guaranteed
// not to be an alias, or any components which are aliases of
// other objects.
// If the root object is a literal don't consider this a
// side-effect.
while (isGet(current)) {
current = current.getFirstChild();
}
return !isLiteralValue(current, true);
} else {
// TODO(johnlenz): remove this code and make this an exception. This
// is here only for legacy reasons, the AST is not valid but
// preserve existing behavior.
return !isLiteralValue(assignTarget, true);
}
}
return true;
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (checkForStateChangeHelper(c, checkForNewObjects, compiler)) {
return true;
}
}
return false;
}
/**
* Do calls to this constructor have side effects?
*
* @param callNode - constructor call node
*/
static boolean constructorCallHasSideEffects(Node callNode) {
return constructorCallHasSideEffects(callNode, null);
}
static boolean constructorCallHasSideEffects(
Node callNode, AbstractCompiler compiler) {
if (!callNode.isNew()) {
throw new IllegalStateException(
"Expected NEW node, got " + Token.name(callNode.getType()));
}
if (callNode.isNoSideEffectsCall()) {
return false;
}
Node nameNode = callNode.getFirstChild();
if (nameNode.isName() &&
CONSTRUCTORS_WITHOUT_SIDE_EFFECTS.contains(nameNode.getString())) {
return false;
}
return true;
}
// A list of built-in object creation or primitive type cast functions that
// can also be called as constructors but lack side-effects.
// TODO(johnlenz): consider adding an extern annotation for this.
private static final Set<String> BUILTIN_FUNCTIONS_WITHOUT_SIDEEFFECTS =
ImmutableSet.of(
"Object", "Array", "String", "Number", "Boolean", "RegExp", "Error");
private static final Set<String> OBJECT_METHODS_WITHOUT_SIDEEFFECTS =
ImmutableSet.of("toString", "valueOf");
private static final Set<String> REGEXP_METHODS =
ImmutableSet.of("test", "exec");
private static final Set<String> STRING_REGEXP_METHODS =
ImmutableSet.of("match", "replace", "search", "split");
/**
* Returns true if calls to this function have side effects.
*
* @param
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> callNode - function call node
*/
static boolean functionCallHasSideEffects(Node callNode) {
return functionCallHasSideEffects(callNode, null);
}
/**
* Returns true if calls to this function have side effects.
*
* @param callNode The call node to inspected.
* @param compiler A compiler object to provide program state changing
* context information. Can be null.
*/
static boolean functionCallHasSideEffects(
Node callNode, @Nullable AbstractCompiler compiler) {
if (!callNode.isCall()) {
throw new IllegalStateException(
"Expected CALL node, got " + Token.name(callNode.getType()));
}
if (callNode.isNoSideEffectsCall()) {
return false;
}
Node nameNode = callNode.getFirstChild();
// Built-in functions with no side effects.
if (nameNode.isName()) {
String name = nameNode.getString();
if (BUILTIN_FUNCTIONS_WITHOUT_SIDEEFFECTS.contains(name)) {
return false;
}
} else if (nameNode.isGetProp()) {
if (callNode.hasOneChild()
&& OBJECT_METHODS_WITHOUT_SIDEEFFECTS.contains(
nameNode.getLastChild().getString())) {
return false;
}
if (callNode.isOnlyModifiesThisCall()
&& evaluatesToLocalValue(nameNode.getFirstChild())) {
return false;
}
// Math.floor has no side-effects.
// TODO(nicksantos): This is a terrible terrible hack, until
// I create a definitionProvider that understands namespacing.
if (nameNode.getFirstChild().isName()) {
if ("Math.floor".equals(nameNode.getQualifiedName())) {
return false;
}
}
if (compiler != null && !compiler.hasRegExpGlobalReferences()) {
if (nameNode.getFirstChild().isRegExp()
&& REGEXP_METHODS.contains(nameNode.getLastChild().getString())) {
return false;
} else if (nameNode.getFirstChild().isString()
&& STRING_REGEXP_METHODS.contains(
nameNode.getLastChild().getString())) {
Node param = nameNode.getNext();
if (param != null &&
(param.isString() || param.isRegExp()))
return false;
}
}
}
return true;
}
/**
* @return Whether the call has a local result.
*/
static boolean callHasLocalResult(Node n) {
Preconditions.checkState(n.isCall());
return (n.getSideEffectFlags() & Node.FLAG_LOCAL_RESULTS) > 0;
}
/**
* @return Whether the new has a local result.
*/
static boolean newHasLocalResult(Node n) {
Preconditions.checkState(n.isNew());
return n.isOnlyModifiesThisCall();
}
/**
*
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> Returns true if the current node's type implies side effects.
*
* This is a non-recursive version of the may have side effects
* check; used to check wherever the current node's type is one of
* the reason's why a subtree has side effects.
*/
static boolean nodeTypeMayHaveSideEffects(Node n) {
return nodeTypeMayHaveSideEffects(n, null);
}
static boolean nodeTypeMayHaveSideEffects(Node n, AbstractCompiler compiler) {
if (isAssignmentOp(n)) {
return true;
}
switch(n.getType()) {
case Token.DELPROP:
case Token.DEC:
case Token.INC:
case Token.THROW:
return true;
case Token.CALL:
return NodeUtil.functionCallHasSideEffects(n, compiler);
case Token.NEW:
return NodeUtil.constructorCallHasSideEffects(n, compiler);
case Token.NAME:
// A variable definition.
return n.hasChildren();
default:
return false;
}
}
/**
* @return Whether the tree can be affected by side-effects or
* has side-effects.
*/
static boolean canBeSideEffected(Node n) {
Set<String> emptySet = Collections.emptySet();
return canBeSideEffected(n, emptySet);
}
/**
* @param knownConstants A set of names known to be constant value at
* node 'n' (such as locals that are last written before n can execute).
* @return Whether the tree can be affected by side-effects or
* has side-effects.
*/
static boolean canBeSideEffected(Node n, Set<String> knownConstants) {
switch (n.getType()) {
case Token.CALL:
case Token.NEW:
// Function calls or constructor can reference changed values.
// TODO(johnlenz): Add some mechanism for determining that functions
// are unaffected by side effects.
return true;
case Token.NAME:
// Non-constant names values may have been changed.
return !isConstantName(n)
&& !knownConstants.contains(n.getString());
// Properties on constant NAMEs can still be side-effected.
case Token.GETPROP:
case Token.GETELEM:
return true;
case Token.FUNCTION:
// Function expression are not changed by side-effects,
// and function declarations are not part of expressions.
Preconditions.checkState(isFunctionExpression(n));
return false;
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (canBeSideEffected(c, knownConstants)) {
return true;
}
}
return false;
}
/*
* 0 comma ,
* 1 assignment = += -= *= /= %= <<= >>= >>>= &= ^= |=
* 2 conditional ?:
* 3 logical-or ||
*
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>Unknown precedence for " +
Token.name(type) +
" (type " + type + ")");
}
}
/**
* Apply the supplied predicate against
* all possible result Nodes of the expression.
*/
static boolean valueCheck(Node n, Predicate<Node> p) {
switch (n.getType()) {
case Token.ASSIGN:
case Token.COMMA:
return valueCheck(n.getLastChild(), p);
case Token.AND:
case Token.OR:
return valueCheck(n.getFirstChild(), p)
&& valueCheck(n.getLastChild(), p);
case Token.HOOK:
return valueCheck(n.getFirstChild().getNext(), p)
&& valueCheck(n.getLastChild(), p);
default:
return p.apply(n);
}
}
static class NumbericResultPredicate implements Predicate<Node> {
@Override
public boolean apply(Node n) {
return isNumericResultHelper(n);
}
}
static final NumbericResultPredicate NUMBERIC_RESULT_PREDICATE =
new NumbericResultPredicate();
/**
* Returns true if the result of node evaluation is always a number
*/
static boolean isNumericResult(Node n) {
return valueCheck(n, NUMBERIC_RESULT_PREDICATE);
}
static boolean isNumericResultHelper(Node n) {
switch (n.getType()) {
case Token.ADD:
return !mayBeString(n.getFirstChild())
&& !mayBeString(n.getLastChild());
case Token.BITNOT:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
case Token.LSH:
case Token.RSH:
case Token.URSH:
case Token.SUB:
case Token.MUL:
case Token.MOD:
case Token.DIV:
case Token.INC:
case Token.DEC:
case Token.POS:
case Token.NEG:
case Token.NUMBER:
return true;
case Token.NAME:
String name = n.getString();
if (name.equals("NaN")) {
return true;
}
if (name.equals("Infinity")) {
return true;
}
return false;
default:
return false;
}
}
static class BooleanResultPredicate implements Predicate<Node> {
@Override
public boolean apply(Node n) {
return isBooleanResultHelper(n);
}
}
static final BooleanResultPredicate BOOLEAN_RESULT_PREDICATE =
new BooleanResultPredicate();
/**
* @return Whether the result of node evaluation is always a boolean
*/
static boolean isBooleanResult(Node n) {
return valueCheck(n, BOOLEAN_RESULT_PREDICATE);
}
static boolean isBooleanResultHelper(Node n) {
switch (n.getType()) {
// Primitives
case Token.TRUE:
case Token.FALSE:
// Comparisons
case Token.EQ
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>:
case Token.NE:
case Token.SHEQ:
case Token.SHNE:
case Token.LT:
case Token.GT:
case Token.LE:
case Token.GE:
// Queries
case Token.IN:
case Token.INSTANCEOF:
// Inversion
case Token.NOT:
// delete operator returns a boolean.
case Token.DELPROP:
return true;
default:
return false;
}
}
static boolean isUndefined(Node n) {
switch (n.getType()) {
case Token.VOID:
return true;
case Token.NAME:
return n.getString().equals("undefined");
}
return false;
}
static boolean isNullOrUndefined(Node n) {
return n.isNull() || isUndefined(n);
}
static class MayBeStringResultPredicate implements Predicate<Node> {
@Override
public boolean apply(Node n) {
return mayBeStringHelper(n);
}
}
static final MayBeStringResultPredicate MAY_BE_STRING_PREDICATE =
new MayBeStringResultPredicate();
/**
* @returns Whether the results is possibly a string.
*/
static boolean mayBeString(Node n) {
return mayBeString(n, true);
}
static boolean mayBeString(Node n, boolean recurse) {
if (recurse) {
return valueCheck(n, MAY_BE_STRING_PREDICATE);
} else {
return mayBeStringHelper(n);
}
}
static boolean mayBeStringHelper(Node n) {
return !isNumericResult(n) && !isBooleanResult(n)
&& !isUndefined(n) && !n.isNull();
}
/**
* Returns true if the operator is associative.
* e.g. (a * b) * c = a * (b * c)
* Note: "+" is not associative because it is also the concatenation
* for strings. e.g. "a" + (1 + 2) is not "a" + 1 + 2
*/
static boolean isAssociative(int type) {
switch (type) {
case Token.MUL:
case Token.AND:
case Token.OR:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return true;
default:
return false;
}
}
/**
* Returns true if the operator is commutative.
* e.g. (a * b) * c = c * (b * a)
* Note 1: "+" is not commutative because it is also the concatenation
* for strings. e.g. "a" + (1 + 2) is not "a" + 1 + 2
* Note 2: only operations on literals and pure functions are commutative.
*/
static boolean isCommutative(int type
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>) {
switch (type) {
case Token.MUL:
case Token.BITOR:
case Token.BITXOR:
case Token.BITAND:
return true;
default:
return false;
}
}
static boolean isAssignmentOp(Node n) {
switch (n.getType()){
case Token.ASSIGN:
case Token.ASSIGN_BITOR:
case Token.ASSIGN_BITXOR:
case Token.ASSIGN_BITAND:
case Token.ASSIGN_LSH:
case Token.ASSIGN_RSH:
case Token.ASSIGN_URSH:
case Token.ASSIGN_ADD:
case Token.ASSIGN_SUB:
case Token.ASSIGN_MUL:
case Token.ASSIGN_DIV:
case Token.ASSIGN_MOD:
return true;
}
return false;
}
static int getOpFromAssignmentOp(Node n) {
switch (n.getType()){
case Token.ASSIGN_BITOR:
return Token.BITOR;
case Token.ASSIGN_BITXOR:
return Token.BITXOR;
case Token.ASSIGN_BITAND:
return Token.BITAND;
case Token.ASSIGN_LSH:
return Token.LSH;
case Token.ASSIGN_RSH:
return Token.RSH;
case Token.ASSIGN_URSH:
return Token.URSH;
case Token.ASSIGN_ADD:
return Token.ADD;
case Token.ASSIGN_SUB:
return Token.SUB;
case Token.ASSIGN_MUL:
return Token.MUL;
case Token.ASSIGN_DIV:
return Token.DIV;
case Token.ASSIGN_MOD:
return Token.MOD;
}
throw new IllegalArgumentException("Not an assignment op:" + n);
}
/**
* Determines if the given node contains a function statement or function
* expression.
*/
static boolean containsFunction(Node n) {
return containsType(n, Token.FUNCTION);
}
/**
* Returns true if the shallow scope contains references to 'this' keyword
*/
static boolean referencesThis(Node n) {
Node start = (n.isFunction()) ? n.getLastChild() : n;
return containsType(start, Token.THIS, MATCH_NOT_FUNCTION);
}
/**
* Is this a GETPROP or GETELEM node?
*/
static boolean isGet(Node n) {
return n.isGetProp() || n.isGetElem();
}
/**
* Is this node the name of a variable being declared?
*
* @param n The node
* @return True if {@code n} is NAME and {@code parent} is VAR
*/
static boolean isVarDeclaration(Node n) {
// There is no need to verify that parent != null because a NAME node
// always has a parent in a valid parse tree.
return n.isName() && n.getParent().isVar();
}
/**
* For an assignment or variable declaration get the assigned value.
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>
* @return The value node representing the new value.
*/
static Node getAssignedValue(Node n) {
Preconditions.checkState(n.isName());
Node parent = n.getParent();
if (parent.isVar()) {
return n.getFirstChild();
} else if (parent.isAssign() && parent.getFirstChild() == n) {
return n.getNext();
} else {
return null;
}
}
/**
* Is this node an assignment expression statement?
*
* @param n The node
* @return True if {@code n} is EXPR_RESULT and {@code n}'s
* first child is ASSIGN
*/
static boolean isExprAssign(Node n) {
return n.isExprResult()
&& n.getFirstChild().isAssign();
}
/**
* Is this node a call expression statement?
*
* @param n The node
* @return True if {@code n} is EXPR_RESULT and {@code n}'s
* first child is CALL
*/
static boolean isExprCall(Node n) {
return n.isExprResult()
&& n.getFirstChild().isCall();
}
/**
* @return Whether the node represents a FOR-IN loop.
*/
static boolean isForIn(Node n) {
return n.isFor()
&& n.getChildCount() == 3;
}
/**
* Determines whether the given node is a FOR, DO, or WHILE node.
*/
static boolean isLoopStructure(Node n) {
switch (n.getType()) {
case Token.FOR:
case Token.DO:
case Token.WHILE:
return true;
default:
return false;
}
}
/**
* @param n The node to inspect.
* @return If the node, is a FOR, WHILE, or DO, it returns the node for
* the code BLOCK, null otherwise.
*/
static Node getLoopCodeBlock(Node n) {
switch (n.getType()) {
case Token.FOR:
case Token.WHILE:
return n.getLastChild();
case Token.DO:
return n.getFirstChild();
default:
return null;
}
}
/**
* @return Whether the specified node has a loop parent that
* is within the current scope.
*/
static boolean isWithinLoop(Node n) {
for (Node parent : n.getAncestors()) {
if (NodeUtil.isLoopStructure(parent)) {
return true;
}
if (parent.isFunction()) {
break;
}
}
return false;
}
/**
* Determines whether the given node is a FOR, DO, WHILE, WITH, or IF node.
*/
static boolean isControlStructure(Node n) {
switch (n.getType()) {
case Token.FOR:
case Token.DO:
case Token.WHILE:
case
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> Token.WITH:
case Token.IF:
case Token.LABEL:
case Token.TRY:
case Token.CATCH:
case Token.SWITCH:
case Token.CASE:
case Token.DEFAULT_CASE:
return true;
default:
return false;
}
}
/**
* Determines whether the given node is code node for FOR, DO,
* WHILE, WITH, or IF node.
*/
static boolean isControlStructureCodeBlock(Node parent, Node n) {
switch (parent.getType()) {
case Token.FOR:
case Token.WHILE:
case Token.LABEL:
case Token.WITH:
return parent.getLastChild() == n;
case Token.DO:
return parent.getFirstChild() == n;
case Token.IF:
return parent.getFirstChild() != n;
case Token.TRY:
return parent.getFirstChild() == n || parent.getLastChild() == n;
case Token.CATCH:
return parent.getLastChild() == n;
case Token.SWITCH:
case Token.CASE:
return parent.getFirstChild() != n;
case Token.DEFAULT_CASE:
return true;
default:
Preconditions.checkState(isControlStructure(parent));
return false;
}
}
/**
* Gets the condition of an ON_TRUE / ON_FALSE CFG edge.
* @param n a node with an outgoing conditional CFG edge
* @return the condition node or null if the condition is not obviously a node
*/
static Node getConditionExpression(Node n) {
switch (n.getType()) {
case Token.IF:
case Token.WHILE:
return n.getFirstChild();
case Token.DO:
return n.getLastChild();
case Token.FOR:
switch (n.getChildCount()) {
case 3:
return null;
case 4:
return n.getFirstChild().getNext();
}
throw new IllegalArgumentException("malformed 'for' statement " + n);
case Token.CASE:
return null;
}
throw new IllegalArgumentException(n + " does not have a condition.");
}
/**
* @return Whether the node is of a type that contain other statements.
*/
static boolean isStatementBlock(Node n) {
return n.isScript() || n.isBlock();
}
/**
* @return Whether the node is used as a statement.
*/
static boolean isStatement(Node n) {
return isStatementParent(n.getParent());
}
static boolean isStatementParent(Node parent) {
// It is not possible to determine definitely if a node is a statement
// or not if it is not part of the AST. A FUNCTION node can be
// either part of an expression or a statement.
Preconditions.checkState(parent != null);
switch (parent.getType()) {
case Token.SCRIPT:
case Token.BLOCK:
case Token.LABEL:
return true;
default:
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>param node A node
* @param parent The node's parent
*/
static boolean isObjectLitKey(Node node, Node parent) {
switch (node.getType()) {
case Token.STRING_KEY:
case Token.GETTER_DEF:
case Token.SETTER_DEF:
return true;
}
return false;
}
/**
* Get the name of an object literal key.
*
* @param key A node
*/
static String getObjectLitKeyName(Node key) {
switch (key.getType()) {
case Token.STRING_KEY:
case Token.GETTER_DEF:
case Token.SETTER_DEF:
return key.getString();
}
throw new IllegalStateException("Unexpected node type: " + key);
}
/**
* @param key A OBJECTLIT key node.
* @return The type expected when using the key.
*/
static JSType getObjectLitKeyTypeFromValueType(Node key, JSType valueType) {
if (valueType != null) {
switch (key.getType()) {
case Token.GETTER_DEF:
// GET must always return a function type.
if (valueType.isFunctionType()) {
FunctionType fntype = valueType.toMaybeFunctionType();
valueType = fntype.getReturnType();
} else {
return null;
}
break;
case Token.SETTER_DEF:
if (valueType.isFunctionType()) {
// SET must always return a function type.
FunctionType fntype = valueType.toMaybeFunctionType();
Node param = fntype.getParametersNode().getFirstChild();
// SET function must always have one parameter.
valueType = param.getJSType();
} else {
return null;
}
break;
}
}
return valueType;
}
/**
* Determines whether a node represents an object literal get or set key
* (e.g. key1 in {get key1() {}, set key2(a){}).
*
* @param node A node
*/
static boolean isGetOrSetKey(Node node) {
switch (node.getType()) {
case Token.GETTER_DEF:
case Token.SETTER_DEF:
return true;
}
return false;
}
/**
* Converts an operator's token value (see {@link Token}) to a string
* representation.
*
* @param operator the operator's token value to convert
* @return the string representation or {@code null} if the token value is
* not an operator
*/
static String opToStr(int operator) {
switch (operator) {
case Token.BITOR: return "|";
case Token.OR: return "||";
case Token.BITXOR: return "^";
case Token.AND: return "&&";
case Token.BITAND: return "&";
case Token.SHEQ: return "===";
case Token.EQ: return "==";
case Token.NOT
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>ares them, if necessary.
*/
static void redeclareVarsInsideBranch(Node branch) {
Collection<Node> vars = getVarsDeclaredInBranch(branch);
if (vars.isEmpty()) {
return;
}
Node parent = getAddingRoot(branch);
for (Node nameNode : vars) {
Node var = IR.var(
IR.name(nameNode.getString())
.srcref(nameNode))
.srcref(nameNode);
copyNameAnnotations(nameNode, var.getFirstChild());
parent.addChildToFront(var);
}
}
/**
* Copy any annotations that follow a named value.
* @param source
* @param destination
*/
static void copyNameAnnotations(Node source, Node destination) {
if (source.getBooleanProp(Node.IS_CONSTANT_NAME)) {
destination.putBooleanProp(Node.IS_CONSTANT_NAME, true);
}
}
/**
* Gets a Node at the top of the current scope where we can add new var
* declarations as children.
*/
private static Node getAddingRoot(Node n) {
Node addingRoot = null;
Node ancestor = n;
while (null != (ancestor = ancestor.getParent())) {
int type = ancestor.getType();
if (type == Token.SCRIPT) {
addingRoot = ancestor;
break;
} else if (type == Token.FUNCTION) {
addingRoot = ancestor.getLastChild();
break;
}
}
// make sure that the adding root looks ok
Preconditions.checkState(addingRoot.isBlock() ||
addingRoot.isScript());
Preconditions.checkState(addingRoot.getFirstChild() == null ||
!addingRoot.getFirstChild().isScript());
return addingRoot;
}
/**
* Creates a node representing a qualified name.
*
* @param name A qualified name (e.g. "foo" or "foo.bar.baz")
* @return A NAME or GETPROP node
*/
public static Node newQualifiedNameNode(
CodingConvention convention, String name) {
int endPos = name.indexOf('.');
if (endPos == -1) {
return newName(convention, name);
}
Node node = newName(convention, name.substring(0, endPos));
int startPos;
do {
startPos = endPos + 1;
endPos = name.indexOf('.', startPos);
String part = (endPos == -1
? name.substring(startPos)
: name.substring(startPos, endPos));
Node propNode = IR.string(part);
if (convention.isConstantKey(part)) {
propNode.putBooleanProp(Node.IS_CONSTANT_NAME, true);
}
node = IR.getprop(node, propNode);
} while (endPos != -1);
return node;
}
/**
* Creates a node representing a qualified name, copying over the source
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> * A predicate for matching name nodes with the specified node.
*/
private static class MatchNameNode implements Predicate<Node>{
final String name;
MatchNameNode(String name){
this.name = name;
}
@Override
public boolean apply(Node n) {
return n.isName() && n.getString().equals(name);
}
}
/**
* A predicate for matching nodes with the specified type.
*/
static class MatchNodeType implements Predicate<Node>{
final int type;
MatchNodeType(int type){
this.type = type;
}
@Override
public boolean apply(Node n) {
return n.getType() == type;
}
}
/**
* A predicate for matching var or function declarations.
*/
static class MatchDeclaration implements Predicate<Node> {
@Override
public boolean apply(Node n) {
return isFunctionDeclaration(n) || n.isVar();
}
}
/**
* A predicate for matching anything except function nodes.
*/
private static class MatchNotFunction implements Predicate<Node>{
@Override
public boolean apply(Node n) {
return !n.isFunction();
}
}
static final Predicate<Node> MATCH_NOT_FUNCTION = new MatchNotFunction();
/**
* A predicate for matching statements without exiting the current scope.
*/
static class MatchShallowStatement implements Predicate<Node>{
@Override
public boolean apply(Node n) {
Node parent = n.getParent();
return n.isBlock()
|| (!n.isFunction() && (parent == null
|| isControlStructure(parent)
|| isStatementBlock(parent)));
}
}
/**
* Finds the number of times a type is referenced within the node tree.
*/
static int getNodeTypeReferenceCount(
Node node, int type, Predicate<Node> traverseChildrenPred) {
return getCount(node, new MatchNodeType(type), traverseChildrenPred);
}
/**
* Whether a simple name is referenced within the node tree.
*/
static boolean isNameReferenced(Node node,
String name,
Predicate<Node> traverseChildrenPred) {
return has(node, new MatchNameNode(name), traverseChildrenPred);
}
/**
* Whether a simple name is referenced within the node tree.
*/
static boolean isNameReferenced(Node node, String name) {
return isNameReferenced(node, name, Predicates.<Node>alwaysTrue());
}
/**
* Finds the number of times a simple name is referenced within the node tree.
*/
static int getNameReferenceCount(Node node, String name) {
return getCount(
node, new MatchNameNode(name), Predicates.<Node>alwaysTrue());
}
/**
* @return Whether the predicate is true for the node or any of its children.
*/
static boolean has(Node node,
Predicate<Node> pred,
Predicate<Node> traverseChildrenPred) {
if (pred.apply(
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> The function.
* @return The Node containing the Function parameters.
*/
public static Node getFunctionParameters(Node fnNode) {
// Function NODE: [ FUNCTION -> NAME, LP -> ARG1, ARG2, ... ]
Preconditions.checkArgument(fnNode.isFunction());
return fnNode.getFirstChild().getNext();
}
/**
* Returns true if a name node represents a constant variable.
*
* <p>Determining whether a variable is constant has three steps:
* <ol>
* <li>In CodingConventionAnnotator, any name that matches the
* {@link CodingConvention#isConstant(String)} is annotated with an
* IS_CONSTANT_NAME property.
* <li>The normalize pass renames any variable with the IS_CONSTANT_NAME
* annotation and that is initialized to a constant value with
* a variable name including $$constant.
* <li>Return true here if the variable includes $$constant in its name.
* </ol>
*
* @param node A NAME or STRING node
* @return True if the variable is constant
*/
static boolean isConstantName(Node node) {
return node.getBooleanProp(Node.IS_CONSTANT_NAME);
}
/** Whether the given name is constant by coding convention. */
static boolean isConstantByConvention(
CodingConvention convention, Node node, Node parent) {
String name = node.getString();
if (parent.isGetProp() &&
node == parent.getLastChild()) {
return convention.isConstantKey(name);
} else if (isObjectLitKey(node, parent)) {
return convention.isConstantKey(name);
} else {
return convention.isConstant(name);
}
}
/**
* Get the JSDocInfo for a function.
*/
public static JSDocInfo getFunctionJSDocInfo(Node n) {
Preconditions.checkState(n.isFunction());
JSDocInfo fnInfo = n.getJSDocInfo();
if (fnInfo == null && NodeUtil.isFunctionExpression(n)) {
// Look for the info on other nodes.
Node parent = n.getParent();
if (parent.isAssign()) {
// on ASSIGNs
fnInfo = parent.getJSDocInfo();
} else if (parent.isName()) {
// on var NAME = function() { ... };
fnInfo = parent.getParent().getJSDocInfo();
}
}
return fnInfo;
}
/**
* @param n The node.
* @return The source name property on the node or its ancestors.
*/
public static String getSourceName(Node n) {
String sourceName = null;
while (sourceName == null && n != null) {
sourceName = n.getSourceFileName();
n = n.getParent();
}
return sourceName;
}
/**
* @param n The node.
* @return The source name
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> property on the node or its ancestors.
*/
public static StaticSourceFile getSourceFile(Node n) {
StaticSourceFile sourceName = null;
while (sourceName == null && n != null) {
sourceName = n.getStaticSourceFile();
n = n.getParent();
}
return sourceName;
}
/**
* @param n The node.
* @return The InputId property on the node or its ancestors.
*/
public static InputId getInputId(Node n) {
while (n != null && !n.isScript()) {
n = n.getParent();
}
return (n != null && n.isScript()) ? n.getInputId() : null;
}
/**
* A new CALL node with the "FREE_CALL" set based on call target.
*/
static Node newCallNode(Node callTarget, Node... parameters) {
boolean isFreeCall = !isGet(callTarget);
Node call = IR.call(callTarget);
call.putBooleanProp(Node.FREE_CALL, isFreeCall);
for (Node parameter : parameters) {
call.addChildToBack(parameter);
}
return call;
}
/**
* @return Whether the node is known to be a value that is not referenced
* elsewhere.
*/
static boolean evaluatesToLocalValue(Node value) {
return evaluatesToLocalValue(value, Predicates.<Node>alwaysFalse());
}
/**
* @param locals A predicate to apply to unknown local values.
* @return Whether the node is known to be a value that is not a reference
* outside the expression scope.
*/
static boolean evaluatesToLocalValue(Node value, Predicate<Node> locals) {
switch (value.getType()) {
case Token.ASSIGN:
// A result that is aliased by a non-local name, is the effectively the
// same as returning a non-local name, but this doesn't matter if the
// value is immutable.
return NodeUtil.isImmutableValue(value.getLastChild())
|| (locals.apply(value)
&& evaluatesToLocalValue(value.getLastChild(), locals));
case Token.COMMA:
return evaluatesToLocalValue(value.getLastChild(), locals);
case Token.AND:
case Token.OR:
return evaluatesToLocalValue(value.getFirstChild(), locals)
&& evaluatesToLocalValue(value.getLastChild(), locals);
case Token.HOOK:
return evaluatesToLocalValue(value.getFirstChild().getNext(), locals)
&& evaluatesToLocalValue(value.getLastChild(), locals);
case Token.INC:
case Token.DEC:
if (value.getBooleanProp(Node.INCRDECR_PROP)) {
return evaluatesToLocalValue(value.getFirstChild(), locals);
} else {
return true;
}
case Token.THIS:
return locals.apply(value);
case Token.NAME:
return isImmutableValue(value) || locals.apply(value);
case
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> Token.GETELEM:
case Token.GETPROP:
// There is no information about the locality of object properties.
return locals.apply(value);
case Token.CALL:
return callHasLocalResult(value)
|| isToStringMethodCall(value)
|| locals.apply(value);
case Token.NEW:
return newHasLocalResult(value)
|| locals.apply(value);
case Token.FUNCTION:
case Token.REGEXP:
case Token.ARRAYLIT:
case Token.OBJECTLIT:
// Literals objects with non-literal children are allowed.
return true;
case Token.DELPROP:
case Token.IN:
// TODO(johnlenz): should IN operator be included in #isSimpleOperator?
return true;
default:
// Other op force a local value:
// x = '' + g (x is now an local string)
// x -= g (x is now an local number)
if (isAssignmentOp(value)
|| isSimpleOperator(value)
|| isImmutableValue(value)) {
return true;
}
throw new IllegalStateException(
"Unexpected expression node" + value +
"\n parent:" + value.getParent());
}
}
/**
* Given the first sibling, this returns the nth
* sibling or null if no such sibling exists.
* This is like "getChildAtIndex" but returns null for non-existent indexes.
*/
private static Node getNthSibling(Node first, int index) {
Node sibling = first;
while (index != 0 && sibling != null) {
sibling = sibling.getNext();
index--;
}
return sibling;
}
/**
* Given the function, this returns the nth
* argument or null if no such parameter exists.
*/
static Node getArgumentForFunction(Node function, int index) {
Preconditions.checkState(function.isFunction());
return getNthSibling(
function.getFirstChild().getNext().getFirstChild(), index);
}
/**
* Given the new or call, this returns the nth
* argument of the call or null if no such argument exists.
*/
static Node getArgumentForCallOrNew(Node call, int index) {
Preconditions.checkState(isCallOrNew(call));
return getNthSibling(
call.getFirstChild().getNext(), index);
}
private static boolean isToStringMethodCall(Node call) {
Node getNode = call.getFirstChild();
if (isGet(getNode)) {
Node propNode = getNode.getLastChild();
return propNode.isString() && "toString".equals(propNode.getString());
}
return false;
}
/** Find the best JSDoc for the given node. */
static JSDocInfo getBestJSDocInfo(Node n) {
JSDocInfo info = n.getJSDocInfo();
if (info == null) {
Node parent = n.getParent();
if (parent == null
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>) {
return null;
}
if (parent.isName()) {
return getBestJSDocInfo(parent);
} else if (parent.isAssign()) {
return parent.getJSDocInfo();
} else if (isObjectLitKey(parent, parent.getParent())) {
return parent.getJSDocInfo();
} else if (parent.isFunction()) {
return parent.getJSDocInfo();
} else if (parent.isVar() && parent.hasOneChild()) {
return parent.getJSDocInfo();
} else if ((parent.isHook() && parent.getFirstChild() != n) ||
parent.isOr() ||
parent.isAnd() ||
(parent.isComma() && parent.getFirstChild() != n)) {
return getBestJSDocInfo(parent);
}
}
return info;
}
/** Find the l-value that the given r-value is being assigned to. */
static Node getBestLValue(Node n) {
Node parent = n.getParent();
boolean isFunctionDeclaration = isFunctionDeclaration(n);
if (isFunctionDeclaration) {
return n.getFirstChild();
} else if (parent.isName()) {
return parent;
} else if (parent.isAssign()) {
return parent.getFirstChild();
} else if (isObjectLitKey(parent, parent.getParent())) {
return parent;
} else if (
(parent.isHook() && parent.getFirstChild() != n) ||
parent.isOr() ||
parent.isAnd() ||
(parent.isComma() && parent.getFirstChild() != n)) {
return getBestLValue(parent);
}
return null;
}
/** Gets the r-value of a node returned by getBestLValue. */
static Node getRValueOfLValue(Node n) {
Node parent = n.getParent();
switch (parent.getType()) {
case Token.ASSIGN:
return n.getNext();
case Token.VAR:
return n.getFirstChild();
case Token.FUNCTION:
return parent;
}
return null;
}
/** Get the owner of the given l-value node. */
static Node getBestLValueOwner(@Nullable Node lValue) {
if (lValue == null || lValue.getParent() == null) {
return null;
}
if (isObjectLitKey(lValue, lValue.getParent())) {
return getBestLValue(lValue.getParent());
} else if (isGet(lValue)) {
return lValue.getFirstChild();
}
return null;
}
/** Get the name of the given l-value node. */
static String getBestLValueName(@Nullable Node lValue) {
if (lValue == null || lValue.getParent() == null) {
return null;
}
if (isObjectLitKey(lValue, lValue.getParent())) {
Node owner =
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> getBestLValue(lValue.getParent());
if (owner != null) {
String ownerName = getBestLValueName(owner);
if (ownerName != null) {
return ownerName + "." + getObjectLitKeyName(lValue);
}
}
return null;
}
return lValue.getQualifiedName();
}
/**
* @returns false iff the result of the expression is not consumed.
*/
static boolean isExpressionResultUsed(Node expr) {
// TODO(johnlenz): consider sharing some code with trySimpleUnusedResult.
Node parent = expr.getParent();
switch (parent.getType()) {
case Token.BLOCK:
case Token.EXPR_RESULT:
return false;
case Token.HOOK:
case Token.AND:
case Token.OR:
return (expr == parent.getFirstChild())
? true : isExpressionResultUsed(parent);
case Token.COMMA:
Node gramps = parent.getParent();
if (gramps.isCall() &&
parent == gramps.getFirstChild()) {
// Semantically, a direct call to eval is different from an indirect
// call to an eval. See ECMA-262 S15.1.2.1. So it's OK for the first
// expression to a comma to be a no-op if it's used to indirect
// an eval. This we pretend that this is "used".
if (expr == parent.getFirstChild() &&
parent.getChildCount() == 2 &&
expr.getNext().isName() &&
"eval".equals(expr.getNext().getString())) {
return true;
}
}
return (expr == parent.getFirstChild())
? false : isExpressionResultUsed(parent);
case Token.FOR:
if (!NodeUtil.isForIn(parent)) {
// Only an expression whose result is in the condition part of the
// expression is used.
return (parent.getChildAtIndex(1) == expr);
}
break;
}
return true;
}
/**
* @param n The expression to check.
* @return Whether the expression is unconditionally executed only once in the
* containing execution scope.
*/
static boolean isExecutedExactlyOnce(Node n) {
inspect: do {
Node parent = n.getParent();
switch (parent.getType()) {
case Token.IF:
case Token.HOOK:
case Token.AND:
case Token.OR:
if (parent.getFirstChild() != n) {
return false;
}
// other ancestors may be conditional
continue inspect;
case Token.FOR:
if (NodeUtil.isForIn(parent)) {
if (parent.getChildAtIndex(1) != n) {
return false;
}
} else {
if (parent.getFirstChild() != n) {
return false;
}
}
// other ancestors may be conditional
continue inspect;
case Token
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> final AbstractCompiler compiler;
@SuppressWarnings("unused")
private boolean inExterns;
InferJSDocInfo(AbstractCompiler compiler) {
this.compiler = compiler;
}
@Override
public void process(Node externs, Node root) {
if (externs != null) {
inExterns = true;
NodeTraversal.traverse(compiler, externs, this);
}
if (root != null) {
inExterns = false;
NodeTraversal.traverse(compiler, root, this);
}
}
@Override
public void hotSwapScript(Node root, Node originalRoot) {
Preconditions.checkNotNull(root);
Preconditions.checkState(root.isScript());
inExterns = false;
NodeTraversal.traverse(compiler, root, this);
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
JSDocInfo docInfo;
switch (n.getType()) {
// Infer JSDocInfo on types of all type declarations on variables.
case Token.NAME:
if (parent == null) {
return;
}
// Only allow JSDoc on VARs, function declarations, and assigns.
if (!parent.isVar() &&
!NodeUtil.isFunctionDeclaration(parent) &&
!(parent.isAssign() &&
n == parent.getFirstChild())) {
return;
}
// There are four places the doc info could live.
// 1) A FUNCTION node.
// /** ... */ function f() { ... }
// 2) An ASSIGN parent.
// /** ... */ x = function () { ... }
// 3) A NAME parent.
// var x, /** ... */ y = function() { ... }
// 4) A VAR gramps.
// /** ... */ var x = function() { ... }
docInfo = n.getJSDocInfo();
if (docInfo == null &&
!(parent.isVar() &&
!parent.hasOneChild())) {
docInfo = parent.getJSDocInfo();
}
// Try to find the type of the NAME.
JSType varType = n.getJSType();
if (varType == null && parent.isFunction()) {
varType = parent.getJSType();
}
// If we have no type to attach JSDocInfo to, then there's nothing
// we can do.
if (varType == null || docInfo == null) {
return;
}
// Dereference the type. If the result is not an object, or already
// has docs attached, then do nothing.
ObjectType objType = dereferenceToObject(varType);
if (objType == null || objType.getJSDocInfo() != null) {
return;
}
attachJSDocInfoToNominalTypeOrShape(objType, docInfo, n.getString());
break;
case Token.GETPROP:
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> // Infer JSDocInfo on properties.
// There are two ways to write doc comments on a property.
//
// 1)
// /** @deprecated */
// obj.prop = ...
//
// 2)
// /** @deprecated */
// obj.prop;
if (parent.isExprResult() ||
(parent.isAssign() &&
parent.getFirstChild() == n)) {
docInfo = n.getJSDocInfo();
if (docInfo == null) {
docInfo = parent.getJSDocInfo();
}
if (docInfo != null) {
ObjectType lhsType =
dereferenceToObject(n.getFirstChild().getJSType());
if (lhsType != null) {
// Put the JSDoc in the property slot, if there is one.
String propName = n.getLastChild().getString();
if (lhsType.hasOwnProperty(propName)) {
lhsType.setPropertyJSDocInfo(propName, docInfo);
}
// Put the JSDoc in any constructors or function shapes as well.
ObjectType propType =
dereferenceToObject(lhsType.getPropertyType(propName));
if (propType != null) {
attachJSDocInfoToNominalTypeOrShape(
propType, docInfo, n.getQualifiedName());
}
}
}
}
break;
}
}
/**
* Dereferences the given type to an object, or returns null.
*/
private ObjectType dereferenceToObject(JSType type) {
return ObjectType.cast(type == null ? null : type.dereference());
}
/**
* Handle cases #1 and #3 in the class doc.
*/
private void attachJSDocInfoToNominalTypeOrShape(
ObjectType objType, JSDocInfo docInfo, @Nullable String qName) {
if (objType.isConstructor() ||
objType.isEnumType() ||
objType.isInterface()) {
// Named types.
if (objType.hasReferenceName() &&
objType.getReferenceName().equals(qName)) {
objType.setJSDocInfo(docInfo);
if (objType.isConstructor() || objType.isInterface()) {
JSType.toMaybeFunctionType(objType).getInstanceType().setJSDocInfo(
docInfo);
} else if (objType instanceof EnumType) {
((EnumType) objType).getElementsType().setJSDocInfo(docInfo);
}
}
} else if (!objType.isNativeObjectType() &&
objType.isFunctionType()) {
// Structural functions.
objType.setJSDocInfo(docInfo);
}
}
}
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>;
private final int depth;
private final Node rootNode;
/** The type of {@code this} in the current scope. */
private final ObjectType thisType;
/** Whether this is a bottom scope for the purposes of type inference. */
private final boolean isBottom;
private Var arguments;
private static final Predicate<Var> DECLARATIVELY_UNBOUND_VARS_WITHOUT_TYPES =
new Predicate<Var>() {
@Override public boolean apply(Var var) {
return var.getParentNode() != null &&
var.getType() == null && // no declared type
var.getParentNode().isVar() &&
!var.isExtern();
}
};
/** Stores info about a variable */
public static class Var
implements StaticSlot<JSType>, StaticReference<JSType> {
/** name */
final String name;
/** Var node */
final Node nameNode;
/**
* The variable's type.
*/
private JSType type;
/**
* The variable's doc info.
*/
private final JSDocInfo info;
/**
* Whether the variable's type has been inferred or is declared. An inferred
* type may change over time (as more code is discovered), whereas a
* declared type is a static contract that must be matched.
*/
private final boolean typeInferred;
/** Input source */
final CompilerInput input;
/** Whether the variable is a define */
final boolean isDefine;
/**
* The index at which the var is declared. e..g if it's 0, it's the first
* declared variable in that scope
*/
final int index;
/** The enclosing scope */
final Scope scope;
private boolean markedEscaped = false;
/**
* Creates a variable.
*
* @param inferred whether its type is inferred (as opposed to declared)
*/
private Var(boolean inferred, String name, Node nameNode, JSType type,
Scope scope, int index, CompilerInput input, boolean isDefine,
JSDocInfo info) {
this.name = name;
this.nameNode = nameNode;
this.type = type;
this.scope = scope;
this.index = index;
this.input = input;
this.isDefine = isDefine;
this.info = info;
this.typeInferred = inferred;
}
/**
* Gets the name of the variable.
*/
@Override
public String getName() {
return name;
}
/**
* Gets the node for the name of the variable.
*/
@Override
public Node getNode() {
return nameNode;
}
CompilerInput getInput() {
return input;
}
@Override
public StaticSourceFile getSourceFile() {
return nameNode.getStaticSourceFile();
}
@Override
public Var getSymbol() {
return this;
}
@Override
public Var getDeclaration() {
return nameNode == null ? null : this;
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> }
/**
* Gets the parent of the name node.
*/
public Node getParentNode() {
return nameNode == null ? null : nameNode.getParent();
}
/**
* Whether this is a bleeding function (an anonymous named function
* that bleeds into the inner scope.
*/
public boolean isBleedingFunction() {
return NodeUtil.isFunctionExpression(getParentNode());
}
/**
* Gets the scope where this variable is declared.
*/
Scope getScope() {
return scope;
}
/**
* Returns whether this is a global variable.
*/
public boolean isGlobal() {
return scope.isGlobal();
}
/**
* Returns whether this is a local variable.
*/
public boolean isLocal() {
return scope.isLocal();
}
/**
* Returns whether this is defined in an extern file.
*/
boolean isExtern() {
return input == null || input.isExtern();
}
/**
* Returns {@code true} if the variable is declared as a constant,
* based on the value reported by {@code NodeUtil}.
*/
public boolean isConst() {
return nameNode != null && NodeUtil.isConstantName(nameNode);
}
/**
* Returns {@code true} if the variable is declared as a define.
* A variable is a define if it is annotated by {@code @define}.
*/
public boolean isDefine() {
return isDefine;
}
public Node getInitialValue() {
Node parent = getParentNode();
int pType = parent.getType();
if (pType == Token.FUNCTION) {
return parent;
} else if (pType == Token.ASSIGN) {
return parent.getLastChild();
} else if (pType == Token.VAR) {
return nameNode.getFirstChild();
} else {
return null;
}
}
/**
* Gets this variable's type. To know whether this type has been inferred,
* see {@code #isTypeInferred()}.
*/
@Override
public JSType getType() {
return type;
}
/**
* Returns the name node that produced this variable.
*/
public Node getNameNode() {
return nameNode;
}
/**
* Gets the JSDocInfo for the variable.
*/
@Override
public JSDocInfo getJSDocInfo() {
return info;
}
/**
* Sets this variable's type.
* @throws IllegalStateException if the variable's type is not inferred
*/
void setType(JSType type) {
Preconditions.checkState(isTypeInferred());
this.type = type;
}
/**
* Resolve this variable's type.
*/
void resolveType(ErrorReporter errorReporter) {
if (type != null) {
type = type.resolve(errorReporter, scope);
}
}
/**
* Returns whether this variable's type is inferred. To get the variable's
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>
* type, see {@link #getType()}.
*/
@Override
public boolean isTypeInferred() {
return typeInferred;
}
public String getInputName() {
if (input == null)
return "<non-file>";
else
return input.getName();
}
public boolean isNoShadow() {
if (info != null && info.isNoShadow()) {
return true;
} else {
return false;
}
}
@Override public boolean equals(Object other) {
if (!(other instanceof Var)) {
return false;
}
Var otherVar = (Var) other;
return otherVar.nameNode == nameNode;
}
@Override public int hashCode() {
return nameNode.hashCode();
}
@Override
public String toString() {
return "Scope.Var " + name + "{" + type + "}";
}
/** Record that this is escaped by an inner scope. */
void markEscaped() {
markedEscaped = true;
}
/**
* Whether this is escaped by an inner scope.
* Notice that not all scope creators record this information.
*/
boolean isMarkedEscaped() {
return markedEscaped;
}
}
/**
* A special subclass of Var used to distinguish "arguments" in the current
* scope.
*/
// TODO(johnlenz): Include this the list of Vars for the scope.
public static class Arguments extends Var {
Arguments(Scope scope) {
super(
false, // no inferred
"arguments", // always arguments
null, // no declaration node
// TODO(johnlenz): provide the type of "Arguments".
null, // no type info
scope,
-1, // no variable index
null, // input,
false, // not a define
null // no jsdoc
);
}
@Override public boolean equals(Object other) {
if (!(other instanceof Arguments)) {
return false;
}
Arguments otherVar = (Arguments) other;
return otherVar.scope.getRootNode() == scope.getRootNode();
}
@Override public int hashCode() {
return System.identityHashCode(this);
}
}
/**
* Creates a Scope given the parent Scope and the root node of the scope.
* @param parent The parent Scope. Cannot be null.
* @param rootNode Typically the FUNCTION node.
*/
Scope(Scope parent, Node rootNode) {
Preconditions.checkNotNull(parent);
Preconditions.checkArgument(rootNode != parent.rootNode);
this.parent = parent;
this.rootNode = rootNode;
JSType nodeType = rootNode.getJSType();
if (nodeType != null && nodeType.isFunctionType()) {
thisType = nodeType.toMaybeFunctionType().getTypeOfThis();
} else {
thisType = parent.thisType;
}
this.isBottom = false;
this.depth = parent
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> null);
// native variables do not have a name node.
JSDocInfo info = nameNode == null
? null : NodeUtil.getBestJSDocInfo(nameNode);
Var var = new Var(inferred, name, nameNode, type, this, vars.size(), input,
info != null && info.isDefine(), info);
vars.put(name, var);
return var;
}
/**
* Undeclares a variable, to be used when the compiler optimizes out
* a variable and removes it from the scope.
*/
void undeclare(Var var) {
Preconditions.checkState(var.scope == this);
Preconditions.checkState(vars.get(var.name) == var);
vars.remove(var.name);
}
@Override
public StaticSlot<JSType> getSlot(String name) {
return getVar(name);
}
@Override
public StaticSlot<JSType> getOwnSlot(String name) {
return vars.get(name);
}
/**
* Returns the variable, may be null
*/
public Var getVar(String name) {
Var var = vars.get(name);
if (var != null) {
return var;
} else if (parent != null) { // Recurse up the parent Scope
return parent.getVar(name);
} else {
return null;
}
}
/**
* Get a unique VAR object to represents "arguments" within this scope
*/
public Var getArgumentsVar() {
if (arguments == null) {
arguments = new Arguments(this);
}
return arguments;
}
/**
* Returns true if a variable is declared.
*/
public boolean isDeclared(String name, boolean recurse) {
Scope scope = this;
if (scope.vars.containsKey(name))
return true;
if (scope.parent != null && recurse) {
return scope.parent.isDeclared(name, recurse);
}
return false;
}
/**
* Return an iterator over all of the variables declared in this scope.
*/
public Iterator<Var> getVars() {
return vars.values().iterator();
}
/**
* Return an iterable over all of the variables declared in this scope.
*/
Iterable<Var> getVarIterable() {
return vars.values();
}
@Override
public Iterable<Var> getReferences(Var var) {
return ImmutableList.of(var);
}
@Override
public StaticScope<JSType> getScope(Var var) {
return var.scope;
}
@Override
public Iterable<Var> getAllSymbols() {
return Collections.unmodifiableCollection(vars.values());
}
/**
* Returns number of variables in this scope
*/
public int getVarCount() {
return vars.size();
}
/**
* Returns whether this is the global scope.
*/
public boolean isGlobal() {
return parent
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>() {
return true;
}
@Override
public <T> T visit(Visitor<T> visitor) {
return visitor.caseAllType();
}
@Override
public BooleanLiteralSet getPossibleToBooleanOutcomes() {
return BooleanLiteralSet.BOTH;
}
@Override
JSType resolveInternal(ErrorReporter t, StaticScope<JSType> scope) {
return this;
}
}
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>Info == null) {
compiler.report(JSError.make(UNKNOWN_TWEAK_WARNING, tweakId));
} else {
TweakFunction registerFunc = tweakInfo.registerCall.tweakFunc;
Node value = entry.getValue();
if (!registerFunc.isValidNodeType(value.getType())) {
compiler.report(JSError.make(INVALID_TWEAK_DEFAULT_VALUE_WARNING,
tweakId, registerFunc.getName(),
registerFunc.getExpectedTypeName()));
} else {
tweakInfo.defaultValueNode = value;
}
}
}
}
/**
* Finds all calls to goog.tweak functions and emits warnings/errors if any
* of the calls have issues.
* @return A map of {@link TweakInfo} structures, keyed by tweak ID.
*/
private CollectTweaksResult collectTweaks(Node root) {
CollectTweaks pass = new CollectTweaks();
NodeTraversal.traverse(compiler, root, pass);
Map<String, TweakInfo> tweakInfos = pass.allTweaks;
for (TweakInfo tweakInfo: tweakInfos.values()) {
tweakInfo.emitAllWarnings();
}
return new CollectTweaksResult(tweakInfos, pass.getOverridesCalls);
}
private final static class CollectTweaksResult {
final Map<String, TweakInfo> tweakInfos;
final List<TweakFunctionCall> getOverridesCalls;
CollectTweaksResult(Map<String, TweakInfo> tweakInfos,
List<TweakFunctionCall> getOverridesCalls) {
this.tweakInfos = tweakInfos;
this.getOverridesCalls = getOverridesCalls;
}
}
/**
* Processes all calls to goog.tweak functions.
*/
private final class CollectTweaks extends AbstractPostOrderCallback {
final Map<String, TweakInfo> allTweaks = Maps.newHashMap();
final List<TweakFunctionCall> getOverridesCalls = Lists.newArrayList();
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
if (!n.isCall()) {
return;
}
String callName = n.getFirstChild().getQualifiedName();
TweakFunction tweakFunc = TWEAK_FUNCTIONS_MAP.get(callName);
if (tweakFunc == null) {
return;
}
if (tweakFunc == TweakFunction.GET_COMPILER_OVERRIDES) {
getOverridesCalls.add(
new TweakFunctionCall(t.getSourceName(), tweakFunc, n));
return;
}
// Ensure the first parameter (the tweak ID) is a string literal.
Node tweakIdNode = n.getFirstChild().getNext();
if (!tweakIdNode.isString()) {
compiler.report(t.makeError(tweakIdNode, NON_LITERAL_TWEAK_ID_ERROR));
return;
}
String tweakId = tweakIdNode.getString();
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> }
Node getIdNode() {
return callNode.getFirstChild().getNext();
}
}
/**
* Stores information about a single tweak.
*/
private final class TweakInfo {
final String tweakId;
final List<TweakFunctionCall> functionCalls;
TweakFunctionCall registerCall;
Node defaultValueNode;
TweakInfo(String tweakId) {
this.tweakId = tweakId;
functionCalls = Lists.newArrayList();
}
/**
* If this tweak is registered, then looks for type warnings in default
* value parameters and getter functions. If it is not registered, emits an
* error for each function call.
*/
void emitAllWarnings() {
if (isRegistered()) {
emitAllTypeWarnings();
} else {
emitUnknownTweakErrors();
}
}
/**
* Emits a warning for each default value parameter that has the wrong type
* and for each getter function that was used for the wrong type of tweak.
*/
void emitAllTypeWarnings() {
for (TweakFunctionCall call : functionCalls) {
Node valueNode = call.valueNode;
TweakFunction tweakFunc = call.tweakFunc;
TweakFunction registerFunc = registerCall.tweakFunc;
if (valueNode != null) {
// For register* and overrideDefaultValue calls, ensure the default
// value is a literal of the correct type.
if (!registerFunc.isValidNodeType(valueNode.getType())) {
compiler.report(JSError.make(call.sourceName,
valueNode, INVALID_TWEAK_DEFAULT_VALUE_WARNING,
tweakId, registerFunc.getName(),
registerFunc.getExpectedTypeName()));
}
} else if (tweakFunc.isGetterFunction()) {
// For getter calls, ensure the correct getter was used.
if (!tweakFunc.isCorrectRegisterFunction(registerFunc)) {
compiler.report(JSError.make(call.sourceName,
call.callNode, TWEAK_WRONG_GETTER_TYPE_WARNING,
tweakFunc.getName(), registerFunc.getName()));
}
}
}
}
/**
* Emits an error for each function call that was found.
*/
void emitUnknownTweakErrors() {
for (TweakFunctionCall call : functionCalls) {
compiler.report(JSError.make(call.sourceName,
call.getIdNode(), UNKNOWN_TWEAK_WARNING, tweakId));
}
}
void addRegisterCall(String sourceName, TweakFunction tweakFunc,
Node callNode, Node defaultValueNode) {
registerCall = new TweakFunctionCall(sourceName, tweakFunc, callNode,
defaultValueNode);
functionCalls.add(registerCall);
}
void addOverrideDefaultValueCall(String sourceName,
TweakFunction tweakFunc, Node callNode, Node defaultValueNode) {
functionCalls.add(new TweakFunctionCall(sourceName, tweakFunc, callNode,
defaultValueNode
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>
return primitiveObjectType == null ?
false : primitiveObjectType.isPropertyTypeDeclared(propertyName);
}
@Override
public boolean isPropertyTypeInferred(String propertyName) {
return primitiveObjectType == null ?
false : primitiveObjectType.isPropertyTypeInferred(propertyName);
}
@Override
public ObjectType getImplicitPrototype() {
return null;
}
@Override
public int getPropertiesCount() {
return primitiveObjectType == null ?
0 : primitiveObjectType.getPropertiesCount();
}
@Override
void collectPropertyNames(Set<String> props) {
if (primitiveObjectType != null) {
primitiveObjectType.collectPropertyNames(props);
}
}
@Override
public JSType findPropertyType(String propertyName) {
return primitiveType.findPropertyType(propertyName);
}
@Override
public JSType getPropertyType(String propertyName) {
return primitiveObjectType == null ?
getNativeType(JSTypeNative.UNKNOWN_TYPE) :
primitiveObjectType.getPropertyType(propertyName);
}
@Override
public boolean hasProperty(String propertyName) {
return primitiveObjectType == null ?
false :
primitiveObjectType.hasProperty(propertyName);
}
@Override
public FunctionType getConstructor() {
return primitiveObjectType == null ?
null : primitiveObjectType.getConstructor();
}
@Override
public JSType autoboxesTo() {
return primitiveType.autoboxesTo();
}
/**
* Gets the primitive type of this enum element.
*/
public JSType getPrimitiveType() {
return primitiveType;
}
/**
* Returns the infimum of a enum element type and another type, or null
* if the infimum is empty.
*
* This can be a little bit weird. For example, suppose you have an enum
* of {(string|number)}, and you want the greatest subtype of the enum
* and a {number}.
*
* The infimum is non-empty. But at the same time, we don't really have
* a name for this infimum. It's equivalent to "elements of this enum that
* are numbers".
*
* The best we can do is make up a new type. This is similar to what
* we do in UnionType#meet, which kind-of-sort-of makes sense, because
* an EnumElementType is a union of instances of a type.
*/
JSType meet(JSType that) {
JSType meetPrimitive = primitiveType.getGreatestSubtype(that);
if (meetPrimitive.isEmptyType()) {
return null;
} else {
return new EnumElementType(registry, meetPrimitive, name);
}
}
@Override
JSType resolveInternal(ErrorReporter t, StaticScope<JSType> scope) {
primitiveType = primitiveType.resolve(t, scope);
primitiveObjectType = ObjectType.cast(primitiveType);
return this;
}
}
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>} calls as a signal for inheritance, or
* {@code return} statements as a signal for return type.
*
* NOTE(nicksantos): Organizationally, this feels like it should be in Rhino.
* But it depends on some coding convention stuff that's really part
* of JSCompiler.
*
* @author nicksantos@google.com (Nick Santos)
* @author pascallouis@google.com (Pascal-Louis Perez)
*/
final class FunctionTypeBuilder {
private final String fnName;
private final AbstractCompiler compiler;
private final CodingConvention codingConvention;
private final JSTypeRegistry typeRegistry;
private final Node errorRoot;
private final String sourceName;
private final Scope scope;
private FunctionContents contents = UnknownFunctionContents.get();
private JSType returnType = null;
private boolean returnTypeInferred = false;
private List<ObjectType> implementedInterfaces = null;
private List<ObjectType> extendedInterfaces = null;
private ObjectType baseType = null;
private ObjectType thisType = null;
private boolean isConstructor = false;
private boolean isInterface = false;
private Node parametersNode = null;
private ImmutableList<String> templateTypeNames = ImmutableList.of();
static final DiagnosticType EXTENDS_WITHOUT_TYPEDEF = DiagnosticType.warning(
"JSC_EXTENDS_WITHOUT_TYPEDEF",
"@extends used without @constructor or @interface for {0}");
static final DiagnosticType EXTENDS_NON_OBJECT = DiagnosticType.warning(
"JSC_EXTENDS_NON_OBJECT",
"{0} @extends non-object type {1}");
static final DiagnosticType RESOLVED_TAG_EMPTY = DiagnosticType.warning(
"JSC_RESOLVED_TAG_EMPTY",
"Could not resolve type in {0} tag of {1}");
static final DiagnosticType IMPLEMENTS_WITHOUT_CONSTRUCTOR =
DiagnosticType.warning(
"JSC_IMPLEMENTS_WITHOUT_CONSTRUCTOR",
"@implements used without @constructor or @interface for {0}");
static final DiagnosticType VAR_ARGS_MUST_BE_LAST = DiagnosticType.warning(
"JSC_VAR_ARGS_MUST_BE_LAST",
"variable length argument must be last");
static final DiagnosticType OPTIONAL_ARG_AT_END = DiagnosticType.warning(
"JSC_OPTIONAL_ARG_AT_END",
"optional arguments must be at the end");
static final DiagnosticType INEXISTANT_PARAM = DiagnosticType.warning(
"JSC_INEXISTANT_PARAM",
"parameter {0} does not appear in {1}''s parameter list");
static final DiagnosticType TYPE_REDEFINITION = DiagnosticType.warning(
"JSC_TYPE_REDEFINITION",
"attempted re-definition of type {0}\n"
+ "found : {1}\n"
+ "expected: {2}");
static final DiagnosticType TEMPLATE_TYPE
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>_DUPLICATED = DiagnosticType.warning(
"JSC_TEMPLATE_TYPE_DUPLICATED",
"Only one parameter type must be the template type");
static final DiagnosticType TEMPLATE_TYPE_EXPECTED = DiagnosticType.warning(
"JSC_TEMPLATE_TYPE_EXPECTED",
"The template type must be a parameter type");
static final DiagnosticType THIS_TYPE_NON_OBJECT =
DiagnosticType.warning(
"JSC_THIS_TYPE_NON_OBJECT",
"@this type of a function must be an object\n" +
"Actual type: {0}");
private class ExtendedTypeValidator implements Predicate<JSType> {
@Override
public boolean apply(JSType type) {
ObjectType objectType = ObjectType.cast(type);
if (objectType == null) {
reportWarning(EXTENDS_NON_OBJECT, fnName, type.toString());
return false;
} else if (objectType.isEmptyType()) {
reportWarning(RESOLVED_TAG_EMPTY, "@extends", fnName);
return false;
} else if (objectType.isUnknownType()) {
if (hasMoreTagsToResolve(objectType)) {
return true;
} else {
reportWarning(RESOLVED_TAG_EMPTY, "@extends", fnName);
return false;
}
} else {
return true;
}
}
}
private class ImplementedTypeValidator implements Predicate<JSType> {
@Override
public boolean apply(JSType type) {
ObjectType objectType = ObjectType.cast(type);
if (objectType == null) {
reportError(BAD_IMPLEMENTED_TYPE, fnName);
return false;
} else if (objectType.isEmptyType()) {
reportWarning(RESOLVED_TAG_EMPTY, "@implements", fnName);
return false;
} else if (objectType.isUnknownType()) {
if (hasMoreTagsToResolve(objectType)) {
return true;
} else {
reportWarning(RESOLVED_TAG_EMPTY, "@implements", fnName);
return false;
}
} else {
return true;
}
}
}
private class ThisTypeValidator implements Predicate<JSType> {
@Override
public boolean apply(JSType type) {
// TODO(user): Doing an instanceof check here is too
// restrictive as (Date,Error) is, for instance, an object type
// even though its implementation is a UnionType. Would need to
// create interfaces JSType, ObjectType, FunctionType etc and have
// separate implementation instead of the class hierarchy, so that
// union types can also be object types, etc.
if (!type.restrictByNotNullOrUndefined().isSubtype(
typeRegistry.getNativeType(OBJECT_TYPE))) {
reportWarning(THIS_TYPE_NON_OBJECT, type.toString());
return false;
}
return true;
}
}
/**
* @param fnName The function name.
* @param compiler The compiler.
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> * @param errorRoot The node to associate with any warning generated by
* this builder.
* @param sourceName A source name for associating any warnings that
* we have to emit.
* @param scope The syntactic scope.
*/
FunctionTypeBuilder(String fnName, AbstractCompiler compiler,
Node errorRoot, String sourceName, Scope scope) {
Preconditions.checkNotNull(errorRoot);
this.fnName = fnName == null ? "" : fnName;
this.codingConvention = compiler.getCodingConvention();
this.typeRegistry = compiler.getTypeRegistry();
this.errorRoot = errorRoot;
this.sourceName = sourceName;
this.compiler = compiler;
this.scope = scope;
}
/**
* Sets the contents of this function.
*/
FunctionTypeBuilder setContents(@Nullable FunctionContents contents) {
if (contents != null) {
this.contents = contents;
}
return this;
}
/**
* Infer the parameter and return types of a function from
* the parameter and return types of the function it is overriding.
*
* @param oldType The function being overridden. Does nothing if this is null.
* @param paramsParent The LP node of the function that we're assigning to.
* If null, that just means we're not initializing this to a function
* literal.
*/
FunctionTypeBuilder inferFromOverriddenFunction(
@Nullable FunctionType oldType, @Nullable Node paramsParent) {
if (oldType == null) {
return this;
}
returnType = oldType.getReturnType();
returnTypeInferred = oldType.isReturnTypeInferred();
if (paramsParent == null) {
// Not a function literal.
parametersNode = oldType.getParametersNode();
if (parametersNode == null) {
parametersNode = new FunctionParamBuilder(typeRegistry).build();
}
} else {
// We're overriding with a function literal. Apply type information
// to each parameter of the literal.
FunctionParamBuilder paramBuilder =
new FunctionParamBuilder(typeRegistry);
Iterator<Node> oldParams = oldType.getParameters().iterator();
boolean warnedAboutArgList = false;
boolean oldParamsListHitOptArgs = false;
for (Node currentParam = paramsParent.getFirstChild();
currentParam != null; currentParam = currentParam.getNext()) {
if (oldParams.hasNext()) {
Node oldParam = oldParams.next();
Node newParam = paramBuilder.newParameterFromNode(oldParam);
oldParamsListHitOptArgs = oldParamsListHitOptArgs ||
oldParam.isVarArgs() ||
oldParam.isOptionalArg();
// The subclass method might write its var_args as individual
// arguments.
if (currentParam.getNext() != null && newParam.isVarArgs()) {
newParam.setVarArgs(false);
newParam.setOptionalArg(true);
}
} else {
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> warnedAboutArgList |= addParameter(
paramBuilder,
typeRegistry.getNativeType(UNKNOWN_TYPE),
warnedAboutArgList,
codingConvention.isOptionalParameter(currentParam) ||
oldParamsListHitOptArgs,
codingConvention.isVarArgsParameter(currentParam));
}
}
// Clone any remaining params that aren't in the function literal,
// but make them optional.
while (oldParams.hasNext()) {
paramBuilder.newOptionalParameterFromNode(oldParams.next());
}
parametersNode = paramBuilder.build();
}
return this;
}
/**
* Infer the return type from JSDocInfo.
*/
FunctionTypeBuilder inferReturnType(@Nullable JSDocInfo info) {
if (info != null && info.hasReturnType()) {
returnType = info.getReturnType().evaluate(scope, typeRegistry);
returnTypeInferred = false;
}
return this;
}
/**
* Infer the role of the function (whether it's a constructor or interface)
* and what it inherits from in JSDocInfo.
*/
FunctionTypeBuilder inferInheritance(@Nullable JSDocInfo info) {
if (info != null) {
isConstructor = info.isConstructor();
isInterface = info.isInterface();
// base type
if (info.hasBaseType()) {
if (isConstructor) {
JSType maybeBaseType =
info.getBaseType().evaluate(scope, typeRegistry);
if (maybeBaseType != null &&
maybeBaseType.setValidator(new ExtendedTypeValidator())) {
baseType = (ObjectType) maybeBaseType;
}
} else {
reportWarning(EXTENDS_WITHOUT_TYPEDEF, fnName);
}
}
// implemented interfaces
if (isConstructor || isInterface) {
implementedInterfaces = Lists.newArrayList();
for (JSTypeExpression t : info.getImplementedInterfaces()) {
JSType maybeInterType = t.evaluate(scope, typeRegistry);
if (maybeInterType != null &&
maybeInterType.setValidator(new ImplementedTypeValidator())) {
implementedInterfaces.add((ObjectType) maybeInterType);
}
}
} else if (info.getImplementedInterfaceCount() > 0) {
reportWarning(IMPLEMENTS_WITHOUT_CONSTRUCTOR, fnName);
}
// extended interfaces (for interface only)
if (isInterface) {
extendedInterfaces = Lists.newArrayList();
for (JSTypeExpression t : info.getExtendedInterfaces()) {
JSType maybeInterfaceType = t.evaluate(scope, typeRegistry);
if (maybeInterfaceType != null &&
maybeInterfaceType.setValidator(new ExtendedTypeValidator())) {
extendedInterfaces.add((ObjectType) maybeInterfaceType);
}
}
}
}
return this;
}
/**
* Infers the type of {@code this}.
* @param type The type of this if the info is missing.
*/
FunctionTypeBuilder inferThisType(JSDocInfo info
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>, JSType type) {
// Look at the @this annotation first.
inferThisType(info);
if (thisType == null) {
ObjectType objType = ObjectType.cast(type);
if (objType != null && (info == null || !info.hasType())) {
thisType = objType;
}
}
return this;
}
/**
* Infers the type of {@code this}.
* @param info The JSDocInfo for this function.
*/
FunctionTypeBuilder inferThisType(JSDocInfo info) {
ObjectType maybeThisType = null;
if (info != null && info.hasThisType()) {
maybeThisType = ObjectType.cast(
info.getThisType().evaluate(scope, typeRegistry));
}
if (maybeThisType != null) {
thisType = maybeThisType;
thisType.setValidator(new ThisTypeValidator());
}
return this;
}
/**
* Infer the parameter types from the doc info alone.
*/
FunctionTypeBuilder inferParameterTypes(JSDocInfo info) {
// Create a fake args parent.
Node lp = IR.paramList();
for (String name : info.getParameterNames()) {
lp.addChildToBack(IR.name(name));
}
return inferParameterTypes(lp, info);
}
/**
* Infer the parameter types from the list of argument names and
* the doc info.
*/
FunctionTypeBuilder inferParameterTypes(@Nullable Node argsParent,
@Nullable JSDocInfo info) {
if (argsParent == null) {
if (info == null) {
return this;
} else {
return inferParameterTypes(info);
}
}
// arguments
Node oldParameterType = null;
if (parametersNode != null) {
oldParameterType = parametersNode.getFirstChild();
}
FunctionParamBuilder builder = new FunctionParamBuilder(typeRegistry);
boolean warnedAboutArgList = false;
Set<String> allJsDocParams = (info == null) ?
Sets.<String>newHashSet() :
Sets.newHashSet(info.getParameterNames());
boolean foundTemplateType = false;
boolean isVarArgs = false;
for (Node arg : argsParent.children()) {
String argumentName = arg.getString();
allJsDocParams.remove(argumentName);
// type from JSDocInfo
JSType parameterType = null;
boolean isOptionalParam = isOptionalParameter(arg, info);
isVarArgs = isVarArgsParameter(arg, info);
if (info != null && info.hasParameterType(argumentName)) {
parameterType =
info.getParameterType(argumentName).evaluate(scope, typeRegistry);
} else if (oldParameterType != null &&
oldParameterType.getJSType() != null) {
parameterType = oldParameterType.getJSType();
isOptionalParam = oldParameterType.isOptionalArg();
isVarArgs = oldParameterType
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>.isVarArgs();
} else {
parameterType = typeRegistry.getNativeType(UNKNOWN_TYPE);
}
warnedAboutArgList |= addParameter(
builder, parameterType, warnedAboutArgList,
isOptionalParam,
isVarArgs);
if (oldParameterType != null) {
oldParameterType = oldParameterType.getNext();
}
}
// Copy over any old parameters that aren't in the param list.
if (!isVarArgs) {
while (oldParameterType != null && !isVarArgs) {
builder.newParameterFromNode(oldParameterType);
oldParameterType = oldParameterType.getNext();
}
}
for (String inexistentName : allJsDocParams) {
reportWarning(INEXISTANT_PARAM, inexistentName, fnName);
}
parametersNode = builder.build();
return this;
}
/**
* @return Whether the given param is an optional param.
*/
private boolean isOptionalParameter(
Node param, @Nullable JSDocInfo info) {
if (codingConvention.isOptionalParameter(param)) {
return true;
}
String paramName = param.getString();
return info != null && info.hasParameterType(paramName) &&
info.getParameterType(paramName).isOptionalArg();
}
/**
* Determine whether this is a var args parameter.
* @return Whether the given param is a var args param.
*/
private boolean isVarArgsParameter(
Node param, @Nullable JSDocInfo info) {
if (codingConvention.isVarArgsParameter(param)) {
return true;
}
String paramName = param.getString();
return info != null && info.hasParameterType(paramName) &&
info.getParameterType(paramName).isVarArgs();
}
/**
* Infer the template type from the doc info.
*/
FunctionTypeBuilder inferTemplateTypeName(@Nullable JSDocInfo info) {
if (info != null) {
templateTypeNames = info.getTemplateTypeNames();
typeRegistry.setTemplateTypeNames(templateTypeNames);
}
return this;
}
/**
* Add a parameter to the param list.
* @param builder A builder.
* @param paramType The parameter type.
* @param warnedAboutArgList Whether we've already warned about arg ordering
* issues (like if optional args appeared before required ones).
* @param isOptional Is this an optional parameter?
* @param isVarArgs Is this a var args parameter?
* @return Whether a warning was emitted.
*/
private boolean addParameter(FunctionParamBuilder builder,
JSType paramType, boolean warnedAboutArgList,
boolean isOptional, boolean isVarArgs) {
boolean emittedWarning = false;
if (isOptional) {
// Remembering that an optional parameter has been encountered
// so that if a non optional param is encountered later, an
// error can be reported.
if (!builder.add
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>OptionalParams(paramType) && !warnedAboutArgList) {
reportWarning(VAR_ARGS_MUST_BE_LAST);
emittedWarning = true;
}
} else if (isVarArgs) {
if (!builder.addVarArgs(paramType) && !warnedAboutArgList) {
reportWarning(VAR_ARGS_MUST_BE_LAST);
emittedWarning = true;
}
} else {
if (!builder.addRequiredParams(paramType) && !warnedAboutArgList) {
// An optional parameter was seen and this argument is not an optional
// or var arg so it is an error.
if (builder.hasVarArgs()) {
reportWarning(VAR_ARGS_MUST_BE_LAST);
} else {
reportWarning(OPTIONAL_ARG_AT_END);
}
emittedWarning = true;
}
}
return emittedWarning;
}
/**
* Builds the function type, and puts it in the registry.
*/
FunctionType buildAndRegister() {
if (returnType == null) {
// Infer return types.
// We need to be extremely conservative about this, because of two
// competing needs.
// 1) If we infer the return type of f too widely, then we won't be able
// to assign f to other functions.
// 2) If we infer the return type of f too narrowly, then we won't be
// able to override f in subclasses.
// So we only infer in cases where the user doesn't expect to write
// @return annotations--when it's very obvious that the function returns
// nothing.
if (!contents.mayHaveNonEmptyReturns() &&
!contents.mayHaveSingleThrow() &&
!contents.mayBeFromExterns()) {
returnType = typeRegistry.getNativeType(VOID_TYPE);
returnTypeInferred = true;
}
}
if (returnType == null) {
returnType = typeRegistry.getNativeType(UNKNOWN_TYPE);
}
if (parametersNode == null) {
throw new IllegalStateException(
"All Function types must have params and a return type");
}
FunctionType fnType;
if (isConstructor) {
fnType = getOrCreateConstructor();
} else if (isInterface) {
fnType = typeRegistry.createInterfaceType(
fnName, contents.getSourceNode());
if (getScopeDeclaredIn().isGlobal() && !fnName.isEmpty()) {
typeRegistry.declareType(fnName, fnType.getInstanceType());
}
maybeSetBaseType(fnType);
} else {
fnType = new FunctionBuilder(typeRegistry)
.withName(fnName)
.withSourceNode(contents.getSourceNode())
.withParamsNode(parametersNode)
.withReturnType(returnType, returnTypeInferred)
.withTypeOfThis(thisType)
.withTemplateNames(templateTypeNames)
.build();
maybeSetBaseType(fnType
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>);
}
if (implementedInterfaces != null) {
fnType.setImplementedInterfaces(implementedInterfaces);
}
if (extendedInterfaces != null) {
fnType.setExtendedInterfaces(extendedInterfaces);
}
typeRegistry.clearTemplateTypeNames();
return fnType;
}
private void maybeSetBaseType(FunctionType fnType) {
if (!fnType.isInterface() && baseType != null) {
fnType.setPrototypeBasedOn(baseType);
}
}
/**
* Returns a constructor function either by returning it from the
* registry if it exists or creating and registering a new type. If
* there is already a type, then warn if the existing type is
* different than the one we are creating, though still return the
* existing function if possible. The primary purpose of this is
* that registering a constructor will fail for all built-in types
* that are initialized in {@link JSTypeRegistry}. We a) want to
* make sure that the type information specified in the externs file
* matches what is in the registry and b) annotate the externs with
* the {@link JSType} from the registry so that there are not two
* separate JSType objects for one type.
*/
private FunctionType getOrCreateConstructor() {
FunctionType fnType = typeRegistry.createConstructorType(
fnName, contents.getSourceNode(), parametersNode, returnType);
JSType existingType = typeRegistry.getType(fnName);
if (existingType != null) {
boolean isInstanceObject = existingType.isInstanceType();
if (isInstanceObject || fnName.equals("Function")) {
FunctionType existingFn =
isInstanceObject ?
existingType.toObjectType().getConstructor() :
typeRegistry.getNativeFunctionType(FUNCTION_FUNCTION_TYPE);
if (existingFn.getSource() == null) {
existingFn.setSource(contents.getSourceNode());
}
if (!existingFn.hasEqualCallType(fnType)) {
reportWarning(TYPE_REDEFINITION, fnName,
fnType.toString(), existingFn.toString());
}
return existingFn;
} else {
// We fall through and return the created type, even though it will fail
// to register. We have no choice as we have to return a function. We
// issue an error elsewhere though, so the user should fix it.
}
}
maybeSetBaseType(fnType);
if (getScopeDeclaredIn().isGlobal() && !fnName.isEmpty()) {
typeRegistry.declareType(fnName, fnType.getInstanceType());
}
return fnType;
}
private void reportWarning(DiagnosticType warning, String ... args) {
compiler.report(JSError.make(sourceName, errorRoot, warning, args));
}
private void reportError(DiagnosticType error, String ... args) {
compiler.report(JSError.make(sourceName, errorRoot, error, args));
}
/**
* Determines
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> whether the given JsDoc info declares a function type.
*/
static boolean isFunctionTypeDeclaration(JSDocInfo info) {
return info.getParameterCount() > 0 ||
info.hasReturnType() ||
info.hasThisType() ||
info.isConstructor() ||
info.isInterface();
}
/**
* The scope that we should declare this function in, if it needs
* to be declared in a scope. Notice that TypedScopeCreator takes
* care of most scope-declaring.
*/
private Scope getScopeDeclaredIn() {
int dotIndex = fnName.indexOf(".");
if (dotIndex != -1) {
String rootVarName = fnName.substring(0, dotIndex);
Var rootVar = scope.getVar(rootVarName);
if (rootVar != null) {
return rootVar.getScope();
}
}
return scope;
}
/**
* Check whether a type is resolvable in the future
* If this has a supertype that hasn't been resolved yet, then we can assume
* this type will be OK once the super type resolves.
* @param objectType
* @return true if objectType is resolvable in the future
*/
private static boolean hasMoreTagsToResolve(ObjectType objectType) {
Preconditions.checkArgument(objectType.isUnknownType());
if (objectType.getImplicitPrototype() != null) {
// constructor extends class
if (objectType.getImplicitPrototype().isResolved()) {
return false;
} else {
return true;
}
} else {
// interface extends interfaces
FunctionType ctor = objectType.getConstructor();
if (ctor != null) {
for (ObjectType interfaceType : ctor.getExtendedInterfaces()) {
if (!interfaceType.isResolved()) {
return true;
}
}
}
return false;
}
}
/** Holds data dynamically inferred about functions. */
static interface FunctionContents {
/** Returns the source node of this function. May be null. */
Node getSourceNode();
/** Returns if the function may be in externs. */
boolean mayBeFromExterns();
/** Returns if a return of a real value (not undefined) appears. */
boolean mayHaveNonEmptyReturns();
/** Returns if this consists of a single throw. */
boolean mayHaveSingleThrow();
/** Gets a list of variables in this scope that are escaped. */
Iterable<String> getEscapedVarNames();
/** Gets a list of variables whose properties are escaped. */
Set<String> getEscapedQualifiedNames();
}
static class UnknownFunctionContents implements FunctionContents {
private static UnknownFunctionContents singleton =
new UnknownFunctionContents();
static FunctionContents get() {
return singleton;
}
@Override
public Node getSourceNode() {
return null;
}
@Override
public boolean mayBeFromExterns() {
return true;
}
@Override
public boolean mayHaveNonEmpty
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>().getParent().isExprResult();
if (!isDefined && !isTypedef(ref)) {
if (!isGlobalExpr) {
reportRefToUndefinedName(name, ref);
}
} else if (declaration != null &&
ref.getModule() != declaration.getModule() &&
!moduleGraph.dependsOn(
ref.getModule(), declaration.getModule())) {
reportBadModuleReference(name, ref);
} else {
// Check for late references.
if (ref.scope.isGlobal()) {
// Prototype references are special, because in our reference graph,
// A.prototype counts as a reference to A.
boolean isPrototypeGet = (ref.type == Ref.Type.PROTOTYPE_GET);
Name owner = isPrototypeGet ? name : parent;
boolean singleGlobalParentDecl =
owner != null &&
owner.getDeclaration() != null &&
owner.localSets == 0;
if (singleGlobalParentDecl &&
owner.getDeclaration().preOrderIndex > ref.preOrderIndex) {
String refName = isPrototypeGet
? name.getFullName() + ".prototype"
: name.getFullName();
compiler.report(
JSError.make(ref.source.getName(), ref.node,
NAME_DEFINED_LATE_WARNING,
refName,
owner.getFullName(),
owner.getDeclaration().source.getName(),
String.valueOf(owner.getDeclaration().node.getLineno())));
}
}
}
}
}
private boolean isTypedef(Ref ref) {
// If this is an annotated EXPR-GET, don't do anything.
Node parent = ref.node.getParent();
if (parent.isExprResult()) {
JSDocInfo info = ref.node.getJSDocInfo();
if (info != null && info.hasTypedefType()) {
return true;
}
}
return false;
}
private void reportBadModuleReference(Name name, Ref ref) {
compiler.report(
JSError.make(ref.source.getName(), ref.node, STRICT_MODULE_DEP_QNAME,
ref.getModule().getName(),
name.getDeclaration().getModule().getName(),
name.getFullName()));
}
private void reportRefToUndefinedName(Name name, Ref ref) {
// grab the highest undefined ancestor to output in the warning message.
while (name.parent != null &&
name.parent.globalSets + name.parent.localSets == 0) {
name = name.parent;
}
compiler.report(
JSError.make(ref.getSourceName(), ref.node, level,
UNDEFINED_NAME_WARNING, name.getFullName()));
}
/**
* Checks whether the given name is a property, and whether that property
* must be initialized with its full qualified name.
*/
private boolean propertyMustBeInitializedByFullName(Name name) {
// If an object or function literal in the global namespace is never
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>inedSlot(FlowScope blindScope);
/**
* Look through the given scope, and try to find slots where it doesn't
* have enough type information. Then fill in that type information
* with stuff that we've inferred in the local flow.
*/
void completeScope(StaticScope<JSType> scope);
}
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>int type, String str) {
super(type);
if (null == str) {
throw new IllegalArgumentException("StringNode: str is null");
}
this.str = str;
}
StringNode(int type, String str, int lineno, int charno) {
super(type, lineno, charno);
if (null == str) {
throw new IllegalArgumentException("StringNode: str is null");
}
this.str = str;
}
/**
* returns the string content.
* @return non null.
*/
@Override
public String getString() {
return this.str;
}
/**
* sets the string content.
* @param str the new value. Non null.
*/
@Override
public void setString(String str) {
if (null == str) {
throw new IllegalArgumentException("StringNode: str is null");
}
this.str = str;
}
@Override
boolean isEquivalentTo(Node node, boolean compareJsType, boolean recurse) {
return (super.isEquivalentTo(node, compareJsType, recurse)
&& this.str.equals(((StringNode) node).str));
}
/**
* If the property is not defined, this was not a quoted key. The
* QUOTED_PROP int property is only assigned to STRING tokens used as
* object lit keys.
* @return true if this was a quoted string key in an object literal.
*/
@Override
public boolean isQuotedString() {
return getBooleanProp(QUOTED_PROP);
}
/**
* This should only be called for STRING nodes created in object lits.
*/
@Override
public void setQuotedString() {
putBooleanProp(QUOTED_PROP, true);
}
private String str;
}
// PropListItems must be immutable so that they can be shared.
private interface PropListItem {
int getType();
PropListItem getNext();
PropListItem chain(PropListItem next);
Object getObjectValue();
int getIntValue();
}
private static abstract class AbstractPropListItem
implements PropListItem, Serializable {
private static final long serialVersionUID = 1L;
private final PropListItem next;
private final int propType;
AbstractPropListItem(int propType, PropListItem next) {
this.propType = propType;
this.next = next;
}
@Override
public int getType() {
return propType;
}
@Override
public PropListItem getNext() {
return next;
}
@Override
public abstract PropListItem chain(PropListItem next);
}
// A base class for Object storing props
private static class ObjectPropListItem
extends AbstractPropListItem {
private static final long serialVersionUID = 1L;
private final Object objectValue;
ObjectPropListItem(int propType, Object objectValue, PropListItem next) {
super(propType, next);
this.objectValue = objectValue;
}
@Override
public
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> int getIntValue() {
throw new UnsupportedOperationException();
}
@Override
public Object getObjectValue() {
return objectValue;
}
@Override
public String toString() {
return objectValue == null ? "null" : objectValue.toString();
}
@Override
public PropListItem chain(PropListItem next) {
return new ObjectPropListItem(getType(), objectValue, next);
}
}
// A base class for int storing props
private static class IntPropListItem extends AbstractPropListItem {
private static final long serialVersionUID = 1L;
final int intValue;
IntPropListItem(int propType, int intValue, PropListItem next) {
super(propType, next);
this.intValue = intValue;
}
@Override
public int getIntValue() {
return intValue;
}
@Override
public Object getObjectValue() {
throw new UnsupportedOperationException();
}
@Override
public String toString() {
return String.valueOf(intValue);
}
@Override
public PropListItem chain(PropListItem next) {
return new IntPropListItem(getType(), intValue, next);
}
}
public Node(int nodeType) {
type = nodeType;
parent = null;
sourcePosition = -1;
}
public Node(int nodeType, Node child) {
Preconditions.checkArgument(child.parent == null,
"new child has existing parent");
Preconditions.checkArgument(child.next == null,
"new child has existing sibling");
type = nodeType;
parent = null;
first = last = child;
child.next = null;
child.parent = this;
sourcePosition = -1;
}
public Node(int nodeType, Node left, Node right) {
Preconditions.checkArgument(left.parent == null,
"first new child has existing parent");
Preconditions.checkArgument(left.next == null,
"first new child has existing sibling");
Preconditions.checkArgument(right.parent == null,
"second new child has existing parent");
Preconditions.checkArgument(right.next == null,
"second new child has existing sibling");
type = nodeType;
parent = null;
first = left;
last = right;
left.next = right;
left.parent = this;
right.next = null;
right.parent = this;
sourcePosition = -1;
}
public Node(int nodeType, Node left, Node mid, Node right) {
Preconditions.checkArgument(left.parent == null);
Preconditions.checkArgument(left.next == null);
Preconditions.checkArgument(mid.parent == null);
Preconditions.checkArgument(mid.next == null);
Preconditions.checkArgument(right.parent == null);
Preconditions.checkArgument(right.next == null);
type = nodeType;
parent = null;
first = left;
last = right;
left.next = mid;
left.parent = this
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> children[i - 1].next = children[i];
Preconditions.checkArgument(children[i - 1].parent == null);
children[i - 1].parent = this;
}
Preconditions.checkArgument(children[children.length - 1].parent == null);
children[children.length - 1].parent = this;
if (null != this.last.next) {
// fail early on loops. implies same node in array twice
throw new IllegalArgumentException("duplicate child");
}
}
}
public static Node newNumber(double number) {
return new NumberNode(number);
}
public static Node newNumber(double number, int lineno, int charno) {
return new NumberNode(number, lineno, charno);
}
public static Node newString(String str) {
return new StringNode(Token.STRING, str);
}
public static Node newString(int type, String str) {
return new StringNode(type, str);
}
public static Node newString(String str, int lineno, int charno) {
return new StringNode(Token.STRING, str, lineno, charno);
}
public static Node newString(int type, String str, int lineno, int charno) {
return new StringNode(type, str, lineno, charno);
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
public boolean hasChildren() {
return first != null;
}
public Node getFirstChild() {
return first;
}
public Node getLastChild() {
return last;
}
public Node getNext() {
return next;
}
public Node getChildBefore(Node child) {
if (child == first) {
return null;
}
Node n = first;
while (n.next != child) {
n = n.next;
if (n == null) {
throw new RuntimeException("node is not a child");
}
}
return n;
}
public Node getChildAtIndex(int i) {
Node n = first;
while (i > 0) {
n = n.next;
i--;
}
return n;
}
public int getIndexOfChild(Node child) {
Node n = first;
int i = 0;
while (n != null) {
if (child == n) {
return i;
}
n = n.next;
i++;
}
return -1;
}
public Node getLastSibling() {
Node n = this;
while (n.next != null) {
n = n.next;
}
return n;
}
public void addChildToFront(Node child) {
Preconditions.checkArgument(child.parent == null);
Preconditions.checkArgument(child.next == null);
child.parent = this;
child.next
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> a child from its parent and siblings.
*/
public void removeChild(Node child) {
Node prev = getChildBefore(child);
if (prev == null)
first = first.next;
else
prev.next = child.next;
if (child == last) last = prev;
child.next = null;
child.parent = null;
}
/**
* Detaches child from Node and replaces it with newChild.
*/
public void replaceChild(Node child, Node newChild) {
Preconditions.checkArgument(newChild.next == null,
"The new child node has siblings.");
Preconditions.checkArgument(newChild.parent == null,
"The new child node already has a parent.");
// Copy over important information.
newChild.copyInformationFrom(child);
newChild.next = child.next;
newChild.parent = this;
if (child == first) {
first = newChild;
} else {
Node prev = getChildBefore(child);
prev.next = newChild;
}
if (child == last)
last = newChild;
child.next = null;
child.parent = null;
}
public void replaceChildAfter(Node prevChild, Node newChild) {
Preconditions.checkArgument(prevChild.parent == this,
"prev is not a child of this node.");
Preconditions.checkArgument(newChild.next == null,
"The new child node has siblings.");
Preconditions.checkArgument(newChild.parent == null,
"The new child node already has a parent.");
// Copy over important information.
newChild.copyInformationFrom(prevChild);
Node child = prevChild.next;
newChild.next = child.next;
newChild.parent = this;
prevChild.next = newChild;
if (child == last)
last = newChild;
child.next = null;
child.parent = null;
}
@VisibleForTesting
PropListItem lookupProperty(int propType) {
PropListItem x = propListHead;
while (x != null && propType != x.getType()) {
x = x.getNext();
}
return x;
}
/**
* Clone the properties from the provided node without copying
* the property object. The receiving node may not have any
* existing properties.
* @param other The node to clone properties from.
* @return this node.
*/
public Node clonePropsFrom(Node other) {
Preconditions.checkState(this.propListHead == null,
"Node has existing properties.");
this.propListHead = other.propListHead;
return this;
}
public void removeProp(int propType) {
PropListItem result = removeProp(propListHead, propType);
if (result != propListHead) {
propListHead = result;
}
}
/**
* @param item The item to inspect
*
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> @param propType The property to look for
* @return The replacement list if the property was removed, or
* 'item' otherwise.
*/
private PropListItem removeProp(PropListItem item, int propType) {
if (item == null) {
return null;
} else if (item.getType() == propType) {
return item.getNext();
} else {
PropListItem result = removeProp(item.getNext(), propType);
if (result != item.getNext()) {
return item.chain(result);
} else {
return item;
}
}
}
public Object getProp(int propType) {
if (propType == SOURCENAME_PROP) {
return getSourceFileName();
}
PropListItem item = lookupProperty(propType);
if (item == null) {
return null;
}
return item.getObjectValue();
}
public boolean getBooleanProp(int propType) {
return getIntProp(propType) != 0;
}
/**
* Returns the integer value for the property, or 0 if the property
* is not defined.
*/
public int getIntProp(int propType) {
PropListItem item = lookupProperty(propType);
if (item == null) {
return 0;
}
return item.getIntValue();
}
public int getExistingIntProp(int propType) {
PropListItem item = lookupProperty(propType);
if (item == null) {
throw new IllegalStateException("missing prop: " + propType);
}
return item.getIntValue();
}
public void putProp(int propType, Object value) {
if (propType == SOURCENAME_PROP) {
putProp(
STATIC_SOURCE_FILE, new SimpleSourceFile((String) value, false));
return;
}
removeProp(propType);
if (value != null) {
propListHead = createProp(propType, value, propListHead);
}
}
public void putBooleanProp(int propType, boolean value) {
putIntProp(propType, value ? 1 : 0);
}
public void putIntProp(int propType, int value) {
removeProp(propType);
if (value != 0) {
propListHead = createProp(propType, value, propListHead);
}
}
PropListItem createProp(int propType, Object value, PropListItem next) {
return new ObjectPropListItem(propType, value, next);
}
PropListItem createProp(int propType, int value, PropListItem next) {
return new IntPropListItem(propType, value, next);
}
// Gets all the property types, in sorted order.
private int[] getSortedPropTypes() {
int count = 0;
for (PropListItem x = propListHead; x != null; x = x.getNext()) {
count++;
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> }
int[] keys = new int[count];
for (PropListItem x = propListHead; x != null; x = x.getNext()) {
count--;
keys[count] = x.getType();
}
Arrays.sort(keys);
return keys;
}
/** Can only be called when <tt>getType() == TokenStream.NUMBER</tt> */
public double getDouble() throws UnsupportedOperationException {
if (this.getType() == Token.NUMBER) {
throw new IllegalStateException(
"Number node not created with Node.newNumber");
} else {
throw new UnsupportedOperationException(this + " is not a number node");
}
}
/** Can only be called when <tt>getType() == TokenStream.NUMBER</tt> */
public void setDouble(double s) throws UnsupportedOperationException {
if (this.getType() == Token.NUMBER) {
throw new IllegalStateException(
"Number node not created with Node.newNumber");
} else {
throw new UnsupportedOperationException(this + " is not a string node");
}
}
/** Can only be called when node has String context. */
public String getString() throws UnsupportedOperationException {
if (this.getType() == Token.STRING) {
throw new IllegalStateException(
"String node not created with Node.newString");
} else {
throw new UnsupportedOperationException(this + " is not a string node");
}
}
/** Can only be called when node has String context. */
public void setString(String s) throws UnsupportedOperationException {
if (this.getType() == Token.STRING) {
throw new IllegalStateException(
"String node not created with Node.newString");
} else {
throw new UnsupportedOperationException(this + " is not a string node");
}
}
@Override
public String toString() {
return toString(true, true, true);
}
public String toString(
boolean printSource,
boolean printAnnotations,
boolean printType) {
StringBuilder sb = new StringBuilder();
toString(sb, printSource, printAnnotations, printType);
return sb.toString();
}
private void toString(
StringBuilder sb,
boolean printSource,
boolean printAnnotations,
boolean printType) {
sb.append(Token.name(type));
if (this instanceof StringNode) {
sb.append(' ');
sb.append(getString());
} else if (type == Token.FUNCTION) {
sb.append(' ');
// In the case of JsDoc trees, the first child is often not a string
// which causes exceptions to be thrown when calling toString or
// toStringTree.
if (first == null || first.getType() != Token.NAME) {
sb.append("<invalid>");
} else {
sb.append(first.getString());
}
} else if (type == Token.NUMBER) {
sb.append(' ');
sb.append(getDouble());
}
if (printSource) {
int lineno = getLineno();
if (lineno != -1) {
sb
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> the nodes are equivalent.
*/
NodeMismatch checkTreeTypeAwareEqualsImpl(Node node2) {
// Do a non-recursive equivalents check.
if (!isEquivalentTo(node2, true, false)) {
return new NodeMismatch(this, node2);
}
NodeMismatch res = null;
Node n, n2;
for (n = first, n2 = node2.first;
res == null && n != null;
n = n.next, n2 = n2.next) {
res = n.checkTreeTypeAwareEqualsImpl(n2);
if (res != null) {
return res;
}
}
return res;
}
/** Returns true if this node is equivalent semantically to another */
public boolean isEquivalentTo(Node node) {
return isEquivalentTo(node, false, true);
}
/**
* Returns true if this node is equivalent semantically to another and
* the types are equivalent.
*/
public boolean isEquivalentToTyped(Node node) {
return isEquivalentTo(node, true, true);
}
/**
* @param compareJsType Whether to compare the JSTypes of the nodes.
* @param recurse Whether to compare the children of the current node, if
* not only the the count of the children are compared.
* @return Whether this node is equivalent semantically to the provided node.
*/
boolean isEquivalentTo(Node node, boolean compareJsType, boolean recurse) {
if (type != node.getType()
|| getChildCount() != node.getChildCount()
|| this.getClass() != node.getClass()) {
return false;
}
if (compareJsType && !JSType.isEquivalent(jsType, node.getJSType())) {
return false;
}
if (type == Token.INC || type == Token.DEC) {
int post1 = this.getIntProp(INCRDECR_PROP);
int post2 = node.getIntProp(INCRDECR_PROP);
if (post1 != post2) {
return false;
}
} else if (type == Token.STRING || type == Token.STRING_KEY) {
if (type == Token.STRING_KEY) {
int quoted1 = this.getIntProp(QUOTED_PROP);
int quoted2 = node.getIntProp(QUOTED_PROP);
if (quoted1 != quoted2) {
return false;
}
}
int slashV1 = this.getIntProp(SLASH_V);
int slashV2 = node.getIntProp(SLASH_V);
if (slashV1 != slashV2) {
return false;
}
} else if (type == Token.CALL) {
if (this.getBooleanProp(FREE_CALL) != node.getBooleanProp(FREE_CALL)) {
return false;
}
}
if (recurse) {
Node n, n2;
for
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> (n = first, n2 = node.first;
n != null;
n = n.next, n2 = n2.next) {
if (!n.isEquivalentTo(n2, compareJsType, true)) {
return false;
}
}
}
return true;
}
/**
* This function takes a set of GETPROP nodes and produces a string that is
* each property separated by dots. If the node ultimately under the left
* sub-tree is not a simple name, this is not a valid qualified name.
*
* @return a null if this is not a qualified name, or a dot-separated string
* of the name and properties.
*/
public String getQualifiedName() {
if (type == Token.NAME) {
String name = getString();
return name.isEmpty() ? null : name;
} else if (type == Token.GETPROP) {
String left = getFirstChild().getQualifiedName();
if (left == null) {
return null;
}
return left + "." + getLastChild().getString();
} else if (type == Token.THIS) {
return "this";
} else {
return null;
}
}
/**
* Returns whether a node corresponds to a simple or a qualified name, such as
* <code>x</code> or <code>a.b.c</code> or <code>this.a</code>.
*/
public boolean isQualifiedName() {
switch (getType()) {
case Token.NAME:
return getString().isEmpty() ? false : true;
case Token.THIS:
return true;
case Token.GETPROP:
return getFirstChild().isQualifiedName();
default:
return false;
}
}
/**
* Returns whether a node corresponds to a simple or a qualified name without
* a "this" reference, such as <code>a.b.c</code>, but not <code>this.a</code>
* .
*/
public boolean isUnscopedQualifiedName() {
switch (getType()) {
case Token.NAME:
return getString().isEmpty() ? false : true;
case Token.GETPROP:
return getFirstChild().isUnscopedQualifiedName();
default:
return false;
}
}
// ==========================================================================
// Mutators
/**
* Removes this node from its parent. Equivalent to:
* node.getParent().removeChild();
*/
public Node detachFromParent() {
Preconditions.checkState(parent != null);
parent.removeChild(this);
return this;
}
/**
* Removes the first child of Node. Equivalent to:
* node.removeChild(node.getFirstChild());
*
* @return The removed Node.
*/
public Node removeFirstChild() {
Node child = first;
if (child != null) {
removeChild(child);
}
return child;
}
/**
* @return A Node that is the head of the
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>SourceInfoIfMissingFromForTree(other);
}
return this;
}
//==========================================================================
// Custom annotations
public JSType getJSType() {
return jsType;
}
public void setJSType(JSType jsType) {
this.jsType = jsType;
}
public FileLevelJsDocBuilder getJsDocBuilderForNode() {
return new FileLevelJsDocBuilder();
}
/**
* An inner class that provides back-door access to the license
* property of the JSDocInfo property for this node. This is only
* meant to be used for top-level script nodes where the
* {@link com.google.javascript.jscomp.parsing.JsDocInfoParser} needs to
* be able to append directly to the top-level node, not just the
* current node.
*/
public class FileLevelJsDocBuilder {
public void append(String fileLevelComment) {
JSDocInfo jsDocInfo = getJSDocInfo();
if (jsDocInfo == null) {
// TODO(user): Is there a way to determine whether to
// parse the JsDoc documentation from here?
jsDocInfo = new JSDocInfo(false);
}
String license = jsDocInfo.getLicense();
if (license == null) {
license = "";
}
jsDocInfo.setLicense(license + fileLevelComment);
setJSDocInfo(jsDocInfo);
}
}
/**
* Get the {@link JSDocInfo} attached to this node.
* @return the information or {@code null} if no JSDoc is attached to this
* node
*/
public JSDocInfo getJSDocInfo() {
return (JSDocInfo) getProp(JSDOC_INFO_PROP);
}
/**
* Sets the {@link JSDocInfo} attached to this node.
*/
public void setJSDocInfo(JSDocInfo info) {
putProp(JSDOC_INFO_PROP, info);
}
/**
* Sets whether this node is a variable length argument node. This
* method is meaningful only on {@link Token#NAME} nodes
* used to define a {@link Token#FUNCTION}'s argument list.
*/
public void setVarArgs(boolean varArgs) {
putBooleanProp(VAR_ARGS_NAME, varArgs);
}
/**
* Returns whether this node is a variable length argument node. This
* method's return value is meaningful only on {@link Token#NAME} nodes
* used to define a {@link Token#FUNCTION}'s argument list.
*/
public boolean isVarArgs() {
return getBooleanProp(VAR_ARGS_NAME);
}
/**
* Sets whether this node is an optional argument node. This
* method is meaningful only on {@link Token#NAME} nodes
* used to define a {@link Token#FUNCTION}'s argument list.
*/
public void setOptionalArg
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>(boolean optionalArg) {
putBooleanProp(OPT_ARG_NAME, optionalArg);
}
/**
* Returns whether this node is an optional argument node. This
* method's return value is meaningful only on {@link Token#NAME} nodes
* used to define a {@link Token#FUNCTION}'s argument list.
*/
public boolean isOptionalArg() {
return getBooleanProp(OPT_ARG_NAME);
}
/**
* Sets whether this is a synthetic block that should not be considered
* a real source block.
*/
public void setIsSyntheticBlock(boolean val) {
putBooleanProp(SYNTHETIC_BLOCK_PROP, val);
}
/**
* Returns whether this is a synthetic block that should not be considered
* a real source block.
*/
public boolean isSyntheticBlock() {
return getBooleanProp(SYNTHETIC_BLOCK_PROP);
}
/**
* Sets the ES5 directives on this node.
*/
public void setDirectives(Set<String> val) {
putProp(DIRECTIVES, val);
}
/**
* Returns the set of ES5 directives for this node.
*/
@SuppressWarnings("unchecked")
public Set<String> getDirectives() {
return (Set<String>) getProp(DIRECTIVES);
}
/**
* Adds a warning to be suppressed. This is indistinguishable
* from having a {@code @suppress} tag in the code.
*/
public void addSuppression(String warning) {
if (getJSDocInfo() == null) {
setJSDocInfo(new JSDocInfo(false));
}
getJSDocInfo().addSuppression(warning);
}
/**
* Sets whether this is a synthetic block that should not be considered
* a real source block.
*/
public void setWasEmptyNode(boolean val) {
putBooleanProp(EMPTY_BLOCK, val);
}
/**
* Returns whether this is a synthetic block that should not be considered
* a real source block.
*/
public boolean wasEmptyNode() {
return getBooleanProp(EMPTY_BLOCK);
}
// There are four values of interest:
// global state changes
// this state changes
// arguments state changes
// whether the call throws an exception
// locality of the result
// We want a value of 0 to mean "global state changes and
// unknown locality of result".
final public static int FLAG_GLOBAL_STATE_UNMODIFIED = 1;
final public static int FLAG_THIS_UNMODIFIED = 2;
final public static int FLAG_ARGUMENTS_UNMODIFIED = 4;
final public static int FLAG_NO_THROWS = 8;
final public static int FLAG_LOCAL_RESULTS = 16;
final public static int SIDE_EFFECTS_FLAGS_MASK = 31;
final public static int SIDE_
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>EFFECTS_ALL = 0;
final public static int NO_SIDE_EFFECTS =
FLAG_GLOBAL_STATE_UNMODIFIED
| FLAG_THIS_UNMODIFIED
| FLAG_ARGUMENTS_UNMODIFIED
| FLAG_NO_THROWS;
/**
* Marks this function or constructor call's side effect flags.
* This property is only meaningful for {@link Token#CALL} and
* {@link Token#NEW} nodes.
*/
public void setSideEffectFlags(int flags) {
Preconditions.checkArgument(
getType() == Token.CALL || getType() == Token.NEW,
"setIsNoSideEffectsCall only supports CALL and NEW nodes, got " +
Token.name(getType()));
putIntProp(SIDE_EFFECT_FLAGS, flags);
}
public void setSideEffectFlags(SideEffectFlags flags) {
setSideEffectFlags(flags.valueOf());
}
/**
* Returns the side effects flags for this node.
*/
public int getSideEffectFlags() {
return getIntProp(SIDE_EFFECT_FLAGS);
}
/**
* A helper class for getting and setting the side-effect flags.
* @author johnlenz@google.com (John Lenz)
*/
public static class SideEffectFlags {
private int value = Node.SIDE_EFFECTS_ALL;
public SideEffectFlags() {
}
public SideEffectFlags(int value) {
this.value = value;
}
public int valueOf() {
return value;
}
/** All side-effect occur and the returned results are non-local. */
public void setAllFlags() {
value = Node.SIDE_EFFECTS_ALL;
}
/** No side-effects occur and the returned results are local. */
public void clearAllFlags() {
value = Node.NO_SIDE_EFFECTS | Node.FLAG_LOCAL_RESULTS;
}
public boolean areAllFlagsSet() {
return value == Node.SIDE_EFFECTS_ALL;
}
/**
* Preserve the return result flag, but clear the others:
* no global state change, no throws, no this change, no arguments change
*/
public void clearSideEffectFlags() {
value |= Node.NO_SIDE_EFFECTS;
}
public void setMutatesGlobalState() {
// Modify global means everything must be assumed to be modified.
removeFlag(Node.FLAG_GLOBAL_STATE_UNMODIFIED);
removeFlag(Node.FLAG_ARGUMENTS_UNMODIFIED);
removeFlag(Node.FLAG_THIS_UNMODIFIED);
}
public void setThrows() {
removeFlag(Node.FLAG_NO_THROWS);
}
public void setMutatesThis() {
removeFlag(Node.FLAG_THIS_UNMODIFIED);
}
public void setMutatesArguments() {
removeFlag(Node.FLAG_ARGUMENTS_UNMODIFIED);
}
public void setReturnsT
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>ainted() {
removeFlag(Node.FLAG_LOCAL_RESULTS);
}
private void removeFlag(int flag) {
value &= ~flag;
}
}
/**
* @return Whether the only side-effect is "modifies this"
*/
public boolean isOnlyModifiesThisCall() {
return areBitFlagsSet(
getSideEffectFlags() & Node.NO_SIDE_EFFECTS,
Node.FLAG_GLOBAL_STATE_UNMODIFIED
| Node.FLAG_ARGUMENTS_UNMODIFIED
| Node.FLAG_NO_THROWS);
}
/**
* Returns true if this node is a function or constructor call that
* has no side effects.
*/
public boolean isNoSideEffectsCall() {
return areBitFlagsSet(getSideEffectFlags(), NO_SIDE_EFFECTS);
}
/**
* Returns true if this node is a function or constructor call that
* returns a primitive or a local object (an object that has no other
* references).
*/
public boolean isLocalResultCall() {
return areBitFlagsSet(getSideEffectFlags(), FLAG_LOCAL_RESULTS);
}
/**
* returns true if all the flags are set in value.
*/
private boolean areBitFlagsSet(int value, int flags) {
return (value & flags) == flags;
}
/**
* This should only be called for STRING nodes children of OBJECTLIT.
*/
public boolean isQuotedString() {
return false;
}
/**
* This should only be called for STRING nodes children of OBJECTLIT.
*/
public void setQuotedString() {
throw new IllegalStateException("not a StringNode");
}
static class NodeMismatch {
final Node nodeA;
final Node nodeB;
NodeMismatch(Node nodeA, Node nodeB) {
this.nodeA = nodeA;
this.nodeB = nodeB;
}
@Override
public boolean equals(Object object) {
if (object instanceof NodeMismatch) {
NodeMismatch that = (NodeMismatch) object;
return that.nodeA.equals(this.nodeA) && that.nodeB.equals(this.nodeB);
}
return false;
}
@Override
public int hashCode() {
return Objects.hashCode(nodeA, nodeB);
}
}
/*** AST type check methods ***/
public boolean isAdd() {
return this.getType() == Token.ADD;
}
public boolean isAnd() {
return this.getType() == Token.AND;
}
public boolean isArrayLit() {
return this.getType() == Token.ARRAYLIT;
}
public boolean isAssign() {
return this.getType() == Token.ASSIGN;
}
public boolean isAssignAdd() {
return this.getType() == Token.ASSIGN_ADD;
}
public boolean isBlock() {
return this.getType() == Token.BLOCK;
}
public boolean isBreak() {
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> return this.getType() == Token.BREAK;
}
public boolean isCall() {
return this.getType() == Token.CALL;
}
public boolean isCase() {
return this.getType() == Token.CASE;
}
public boolean isCatch() {
return this.getType() == Token.CATCH;
}
public boolean isComma() {
return this.getType() == Token.COMMA;
}
public boolean isContinue() {
return this.getType() == Token.CONTINUE;
}
public boolean isDebugger() {
return this.getType() == Token.DEBUGGER;
}
public boolean isDec() {
return this.getType() == Token.DEC;
}
public boolean isDefaultCase() {
return this.getType() == Token.DEFAULT_CASE;
}
public boolean isDelProp() {
return this.getType() == Token.DELPROP;
}
public boolean isDo() {
return this.getType() == Token.DO;
}
public boolean isEmpty() {
return this.getType() == Token.EMPTY;
}
public boolean isExprResult() {
return this.getType() == Token.EXPR_RESULT;
}
public boolean isFalse() {
return this.getType() == Token.FALSE;
}
public boolean isFor() {
return this.getType() == Token.FOR;
}
public boolean isFunction() {
return this.getType() == Token.FUNCTION;
}
public boolean isGetterDef() {
return this.getType() == Token.GETTER_DEF;
}
public boolean isGetElem() {
return this.getType() == Token.GETELEM;
}
public boolean isGetProp() {
return this.getType() == Token.GETPROP;
}
public boolean isHook() {
return this.getType() == Token.HOOK;
}
public boolean isIf() {
return this.getType() == Token.IF;
}
public boolean isIn() {
return this.getType() == Token.IN;
}
public boolean isInc() {
return this.getType() == Token.INC;
}
public boolean isInstanceOf() {
return this.getType() == Token.INSTANCEOF;
}
public boolean isLabel() {
return this.getType() == Token.LABEL;
}
public boolean isLabelName() {
return this.getType() == Token.LABEL_NAME;
}
public boolean isName() {
return this.getType() == Token.NAME;
}
public boolean isNE() {
return this.getType() == Token.NE;
}
public boolean isNew() {
return this.getType() == Token.NEW;
}
public boolean isNot() {
return this.getType() == Token.NOT;
}
public boolean isNull() {
return this.getType() == Token.NULL;
}
public boolean isNumber() {
return this.getType() == Token.NUMBER;
}
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> public boolean isObjectLit() {
return this.getType() == Token.OBJECTLIT;
}
public boolean isOr() {
return this.getType() == Token.OR;
}
public boolean isParamList() {
return this.getType() == Token.PARAM_LIST;
}
public boolean isRegExp() {
return this.getType() == Token.REGEXP;
}
public boolean isReturn() {
return this.getType() == Token.RETURN;
}
public boolean isScript() {
return this.getType() == Token.SCRIPT;
}
public boolean isSetterDef() {
return this.getType() == Token.SETTER_DEF;
}
public boolean isString() {
return this.getType() == Token.STRING;
}
public boolean isStringKey() {
return this.getType() == Token.STRING_KEY;
}
public boolean isSwitch() {
return this.getType() == Token.SWITCH;
}
public boolean isThis() {
return this.getType() == Token.THIS;
}
public boolean isThrow() {
return this.getType() == Token.THROW;
}
public boolean isTrue() {
return this.getType() == Token.TRUE;
}
public boolean isTry() {
return this.getType() == Token.TRY;
}
public boolean isTypeOf() {
return this.getType() == Token.TYPEOF;
}
public boolean isVar() {
return this.getType() == Token.VAR;
}
public boolean isVoid() {
return this.getType() == Token.VOID;
}
public boolean isWhile() {
return this.getType() == Token.WHILE;
}
public boolean isWith() {
return this.getType() == Token.WITH;
}
}
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>/*
* Copyright 2008 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.collect.Maps;
import com.google.javascript.jscomp.CheckLevel;
import com.google.javascript.jscomp.NodeTraversal.AbstractShallowCallback;
import com.google.javascript.rhino.JSDocInfo;
import com.google.javascript.rhino.JSDocInfo.Visibility;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import java.util.Map;
/**
* Insures '@constructor X' has a 'goog.provide("X")' .
*
*/
class CheckProvides implements HotSwapCompilerPass {
private final AbstractCompiler compiler;
private final CheckLevel checkLevel;
private final CodingConvention codingConvention;
static final DiagnosticType MISSING_PROVIDE_WARNING = DiagnosticType.disabled(
"JSC_MISSING_PROVIDE",
"missing goog.provide(''{0}'')");
CheckProvides(AbstractCompiler compiler, CheckLevel checkLevel) {
this.compiler = compiler;
this.checkLevel = checkLevel;
this.codingConvention = compiler.getCodingConvention();
}
@Override
public void process(Node externs, Node root) {
hotSwapScript(root, null);
}
@Override
public void hotSwapScript(Node scriptRoot, Node originalRoot) {
CheckProvidesCallback callback =
new CheckProvidesCallback(codingConvention);
new NodeTraversal(compiler, callback).traverse(scriptRoot);
}
private class CheckProvidesCallback extends AbstractShallowCallback {
private final Map<String, Node> provides = Maps.newHashMap();
private final Map<String, Node> ctors = Maps.newHashMap();
private final CodingConvention convention;
CheckProvidesCallback(CodingConvention convention){
this.convention = convention;
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getType()) {
case Token.CALL:
String providedClassName =
codingConvention.extractClassNameIfProvide(n, parent);
if (providedClassName != null) {
provides.put(providedClassName, n);
}
break;
case Token.FUNCTION:
visitFunctionNode(n,
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> parent);
break;
case Token.SCRIPT:
visitScriptNode(t, n);
}
}
private void visitFunctionNode(Node n, Node parent) {
Node name = null;
JSDocInfo info = parent.getJSDocInfo();
if (info != null && info.isConstructor()) {
name = parent.getFirstChild();
} else {
// look to the child, maybe it's a named function
info = n.getJSDocInfo();
if (info != null && info.isConstructor()) {
name = n.getFirstChild();
}
}
if (name != null && name.isQualifiedName()) {
String qualifiedName = name.getQualifiedName();
if (!this.convention.isPrivate(qualifiedName)) {
Visibility visibility = info.getVisibility();
if (!visibility.equals(JSDocInfo.Visibility.PRIVATE)) {
ctors.put(qualifiedName, name);
}
}
}
}
private void visitScriptNode(NodeTraversal t, Node n) {
for (Map.Entry<String, Node> ctorEntry : ctors.entrySet()) {
String ctor = ctorEntry.getKey();
int index = -1;
boolean found = false;
do {
index = ctor.indexOf('.', index +1);
String provideKey = index == -1 ? ctor : ctor.substring(0, index);
if (provides.containsKey(provideKey)) {
found = true;
break;
}
} while (index != -1);
if (!found) {
compiler.report(
t.makeError(ctorEntry.getValue(), checkLevel,
MISSING_PROVIDE_WARNING, ctorEntry.getKey()));
}
}
provides.clear();
ctors.clear();
}
}
}
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>/*
* Copyright 2006 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import com.google.javascript.jscomp.Scope.Var;
import com.google.javascript.jscomp.type.ClosureReverseAbstractInterpreter;
import com.google.javascript.jscomp.type.SemanticReverseAbstractInterpreter;
import com.google.javascript.rhino.InputId;
import com.google.javascript.rhino.Node;
import com.google.javascript.rhino.Token;
import com.google.javascript.rhino.jstype.FunctionType;
import com.google.javascript.rhino.jstype.JSType;
import com.google.javascript.rhino.jstype.JSTypeNative;
import com.google.javascript.rhino.jstype.ObjectType;
import java.util.Arrays;
import java.util.List;
/**
* Tests {@link TypeCheck}.
*
*/
public class TypeCheckTest extends CompilerTypeTestCase {
private CheckLevel reportMissingOverrides = CheckLevel.WARNING;
@Override
public void setUp() throws Exception {
super.setUp();
reportMissingOverrides = CheckLevel.WARNING;
}
public void testInitialTypingScope() {
Scope s = new TypedScopeCreator(compiler,
CodingConventions.getDefault()).createInitialScope(
new Node(Token.BLOCK));
assertEquals(ARRAY_FUNCTION_TYPE, s.getVar("Array").getType());
assertEquals(BOOLEAN_OBJECT_FUNCTION_TYPE,
s.getVar("Boolean").getType());
assertEquals(DATE_FUNCTION_TYPE, s.getVar("Date").getType());
assertEquals(ERROR_FUNCTION_TYPE, s.getVar("Error").getType());
assertEquals(EVAL_ERROR_FUNCTION_TYPE,
s.getVar("EvalError").getType());
assertEquals(NUMBER_OBJECT_FUNCTION_TYPE,
s.getVar("Number").getType());
assertEquals(OBJECT_FUNCTION_TYPE, s.getVar("Object").getType());
assertEquals(RANGE_ERROR_FUNCTION_TYPE,
s.getVar("RangeError").getType());
assertEquals(REFERENCE_ERROR_FUNCTION_TYPE,
s.getVar("ReferenceError").getType());
assertEquals(REGEXP_FUNCTION_TYPE, s.getVar("RegExp").getType());
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>
assertEquals(STRING_OBJECT_FUNCTION_TYPE,
s.getVar("String").getType());
assertEquals(SYNTAX_ERROR_FUNCTION_TYPE,
s.getVar("SyntaxError").getType());
assertEquals(TYPE_ERROR_FUNCTION_TYPE,
s.getVar("TypeError").getType());
assertEquals(URI_ERROR_FUNCTION_TYPE,
s.getVar("URIError").getType());
}
public void testTypeCheck1() throws Exception {
testTypes("/**@return {void}*/function foo(){ if (foo()) return; }");
}
public void testTypeCheck2() throws Exception {
testTypes("/**@return {void}*/function foo(){ var x=foo(); x--; }",
"increment/decrement\n" +
"found : undefined\n" +
"required: number");
}
public void testTypeCheck4() throws Exception {
testTypes("/**@return {void}*/function foo(){ !foo(); }");
}
public void testTypeCheck5() throws Exception {
testTypes("/**@return {void}*/function foo(){ var a = +foo(); }",
"sign operator\n" +
"found : undefined\n" +
"required: number");
}
public void testTypeCheck6() throws Exception {
testTypes(
"/**@return {void}*/function foo(){" +
"/** @type {undefined|number} */var a;if (a == foo())return;}");
}
public void testTypeCheck8() throws Exception {
testTypes("/**@return {void}*/function foo(){do {} while (foo());}");
}
public void testTypeCheck9() throws Exception {
testTypes("/**@return {void}*/function foo(){while (foo());}");
}
public void testTypeCheck10() throws Exception {
testTypes("/**@return {void}*/function foo(){for (;foo(););}");
}
public void testTypeCheck11() throws Exception {
testTypes("/**@type !Number */var a;" +
"/**@type !String */var b;" +
"a = b;",
"assignment\n" +
"found : String\n" +
"required: Number");
}
public void testTypeCheck12() throws Exception {
testTypes("/**@return {!Object}*/function foo(){var a = 3^foo();}",
"bad right operand to bitwise operator\n" +
"found : Object\n" +
"required: (boolean|null|number|string|undefined)");
}
public void testTypeCheck13() throws Exception {
testTypes("/**@type {!Number|!String}*/var i; i=/xx/;",
"assignment\n" +
"found : RegExp\n" +
"required: (Number|String)");
}
public void testTypeCheck14() throws Exception {
testTypes("/**@param opt_a*/function foo(opt_a){}");
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>
"variable b redefined with type String, original " +
"definition at [testcode]:1 with type (Number|null)");
}
public void testScoping5() throws Exception {
// multiple definitions are not checked by the type checker but by a
// subsequent pass
testTypes("if (true) var b; var b;");
}
public void testScoping6() throws Exception {
// multiple definitions are not checked by the type checker but by a
// subsequent pass
testTypes("if (true) var b; if (true) var b;");
}
public void testScoping7() throws Exception {
testTypes("/** @constructor */function A() {" +
" /** @type !A */this.a = null;" +
"}",
"assignment to property a of A\n" +
"found : null\n" +
"required: A");
}
public void testScoping8() throws Exception {
testTypes("/** @constructor */function A() {}" +
"/** @constructor */function B() {" +
" /** @type !A */this.a = null;" +
"}",
"assignment to property a of B\n" +
"found : null\n" +
"required: A");
}
public void testScoping9() throws Exception {
testTypes("/** @constructor */function B() {" +
" /** @type !A */this.a = null;" +
"}" +
"/** @constructor */function A() {}",
"assignment to property a of B\n" +
"found : null\n" +
"required: A");
}
public void testScoping10() throws Exception {
TypeCheckResult p = parseAndTypeCheckWithScope("var a = function b(){};");
// a declared, b is not
assertTrue(p.scope.isDeclared("a", false));
assertFalse(p.scope.isDeclared("b", false));
// checking that a has the correct assigned type
assertEquals("function (): undefined",
p.scope.getVar("a").getType().toString());
}
public void testScoping11() throws Exception {
// named function expressions create a binding in their body only
// the return is wrong but the assignment is OK since the type of b is ?
testTypes(
"/** @return {number} */var a = function b(){ return b };",
"inconsistent return type\n" +
"found : function (): number\n" +
"required: number");
}
public void testScoping12() throws Exception {
testTypes(
"/** @constructor */ function F() {}" +
"/** @type {number} */ F.prototype.bar = 3;" +
"/** @param {!F} f */ function g(f) {" +
" /** @return {string} */" +
" function h
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>
// the var has been dereferenced after the first one.
testTypes("var bar = void 0; bar[0]; bar[1];",
"only arrays or objects can be accessed\n" +
"found : undefined\n" +
"required: Object");
}
public void testArrayAccess9() throws Exception {
testTypes("/** @return {?Array} */ function f() { return []; }" +
"f()[{}]",
"array access\n" +
"found : {}\n" +
"required: number");
}
public void testPropAccess() throws Exception {
testTypes("/** @param {*} x */var f = function(x) {\n" +
"var o = String(x);\n" +
"if (typeof o['a'] != 'undefined') { return o['a']; }\n" +
"return null;\n" +
"};");
}
public void testPropAccess2() throws Exception {
testTypes("var bar = void 0; bar.baz;",
"No properties on this expression\n" +
"found : undefined\n" +
"required: Object");
}
public void testPropAccess3() throws Exception {
// Verifies that we don't emit two warnings, because
// the var has been dereferenced after the first one.
testTypes("var bar = void 0; bar.baz; bar.bax;",
"No properties on this expression\n" +
"found : undefined\n" +
"required: Object");
}
public void testPropAccess4() throws Exception {
testTypes("/** @param {*} x */ function f(x) { return x['hi']; }");
}
public void testSwitchCase1() throws Exception {
testTypes("/**@type number*/var a;" +
"/**@type string*/var b;" +
"switch(a){case b:;}",
"case expression doesn't match switch\n" +
"found : string\n" +
"required: number");
}
public void testSwitchCase2() throws Exception {
testTypes("var a = null; switch (typeof a) { case 'foo': }");
}
public void testVar1() throws Exception {
TypeCheckResult p =
parseAndTypeCheckWithScope("/** @type {(string,null)} */var a = null");
assertEquals(createUnionType(STRING_TYPE, NULL_TYPE),
p.scope.getVar("a").getType());
}
public void testVar2() throws Exception {
testTypes("/** @type {Function} */ var a = function(){}");
}
public void testVar3() throws Exception {
TypeCheckResult p = parseAndTypeCheckWithScope("var a = 3;");
assertEquals(NUMBER_TYPE, p.scope.getVar("a").getType());
}
public void testVar4() throws Exception {
TypeCheckResult p = parse
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>AndTypeCheckWithScope(
"var a = 3; a = 'string';");
assertEquals(createUnionType(STRING_TYPE, NUMBER_TYPE),
p.scope.getVar("a").getType());
}
public void testVar5() throws Exception {
testTypes("var goog = {};" +
"/** @type string */goog.foo = 'hello';" +
"/** @type number */var a = goog.foo;",
"initializing variable\n" +
"found : string\n" +
"required: number");
}
public void testVar6() throws Exception {
testTypes(
"function f() {" +
" return function() {" +
" /** @type {!Date} */" +
" var a = 7;" +
" };" +
"}",
"initializing variable\n" +
"found : number\n" +
"required: Date");
}
public void testVar7() throws Exception {
testTypes("/** @type number */var a, b;",
"declaration of multiple variables with shared type information");
}
public void testVar8() throws Exception {
testTypes("var a, b;");
}
public void testVar9() throws Exception {
testTypes("/** @enum */var a;",
"enum initializer must be an object literal or an enum");
}
public void testVar10() throws Exception {
testTypes("/** @type !Number */var foo = 'abc';",
"initializing variable\n" +
"found : string\n" +
"required: Number");
}
public void testVar11() throws Exception {
testTypes("var /** @type !Date */foo = 'abc';",
"initializing variable\n" +
"found : string\n" +
"required: Date");
}
public void testVar12() throws Exception {
testTypes("var /** @type !Date */foo = 'abc', " +
"/** @type !RegExp */bar = 5;",
new String[] {
"initializing variable\n" +
"found : string\n" +
"required: Date",
"initializing variable\n" +
"found : number\n" +
"required: RegExp"});
}
public void testVar13() throws Exception {
// this caused an NPE
testTypes("var /** @type number */a,a;");
}
public void testVar14() throws Exception {
testTypes("/** @return {number} */ function f() { var x; return x; }",
"inconsistent return type\n" +
"found : undefined\n" +
"required: number");
}
public void testVar15() throws Exception {
testTypes("/** @return {number} */" +
"function f() { var x = x || {}; return x; }",
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>
testTypes("/** @return {(number,undefined)} */ function a() { return 3; }" +
"var b = a() === undefined");
}
/**
* Tests that the match method of strings returns nullable arrays.
*/
public void testBug908701() throws Exception {
testTypes("/** @type {String} */var s = new String('foo');" +
"var b = s.match(/a/) != null;");
}
/**
* Tests that named types play nicely with subtyping.
*/
public void testBug908625() throws Exception {
testTypes("/** @constructor */function A(){}" +
"/** @constructor\n * @extends A */function B(){}" +
"/** @param {B} b" +
"\n @return {(A,undefined)} */function foo(b){return b}");
}
/**
* Tests that assigning two untyped functions to a variable whose type is
* inferred and calling this variable is legal.
*/
public void testBug911118() throws Exception {
// verifying the type assigned to function expressions assigned variables
Scope s = parseAndTypeCheckWithScope("var a = function(){};").scope;
JSType type = s.getVar("a").getType();
assertEquals("function (): undefined", type.toString());
// verifying the bug example
testTypes("function nullFunction() {};" +
"var foo = nullFunction;" +
"foo = function() {};" +
"foo();");
}
public void testBug909000() throws Exception {
testTypes("/** @constructor */function A(){}\n" +
"/** @param {!A} a\n" +
"@return {boolean}*/\n" +
"function y(a) { return a }",
"inconsistent return type\n" +
"found : A\n" +
"required: boolean");
}
public void testBug930117() throws Exception {
testTypes(
"/** @param {boolean} x */function f(x){}" +
"f(null);",
"actual parameter 1 of f does not match formal parameter\n" +
"found : null\n" +
"required: boolean");
}
public void testBug1484445() throws Exception {
testTypes(
"/** @constructor */ function Foo() {}" +
"/** @type {number?} */ Foo.prototype.bar = null;" +
"/** @type {number?} */ Foo.prototype.baz = null;" +
"/** @param {Foo} foo */" +
"function f(foo) {" +
" while (true) {" +
" if (foo.bar == null && foo.baz == null) {" +
" foo.bar;" +
" }" +
" }" +
"}");
}
public
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> Math()", TypeCheck.NOT_A_CONSTRUCTOR);
}
public void testNew3() throws Exception {
testTypes("new Date()");
}
public void testNew4() throws Exception {
testTypes("/** @constructor */function A(){}; new A();");
}
public void testNew5() throws Exception {
testTypes("function A(){}; new A();", TypeCheck.NOT_A_CONSTRUCTOR);
}
public void testNew6() throws Exception {
TypeCheckResult p =
parseAndTypeCheckWithScope("/** @constructor */function A(){};" +
"var a = new A();");
JSType aType = p.scope.getVar("a").getType();
assertTrue(aType instanceof ObjectType);
ObjectType aObjectType = (ObjectType) aType;
assertEquals("A", aObjectType.getConstructor().getReferenceName());
}
public void testNew7() throws Exception {
testTypes("/** @param {Function} opt_constructor */" +
"function foo(opt_constructor) {" +
"if (opt_constructor) { new opt_constructor; }" +
"}");
}
public void testNew8() throws Exception {
testTypes("/** @param {Function} opt_constructor */" +
"function foo(opt_constructor) {" +
"new opt_constructor;" +
"}");
}
public void testNew9() throws Exception {
testTypes("/** @param {Function} opt_constructor */" +
"function foo(opt_constructor) {" +
"new (opt_constructor || Array);" +
"}");
}
public void testNew10() throws Exception {
testTypes("var goog = {};" +
"/** @param {Function} opt_constructor */" +
"goog.Foo = function (opt_constructor) {" +
"new (opt_constructor || Array);" +
"}");
}
public void testNew11() throws Exception {
testTypes("/** @param {Function} c1 */" +
"function f(c1) {" +
" var c2 = function(){};" +
" c1.prototype = new c2;" +
"}", TypeCheck.NOT_A_CONSTRUCTOR);
}
public void testNew12() throws Exception {
TypeCheckResult p = parseAndTypeCheckWithScope("var a = new Array();");
Var a = p.scope.getVar("a");
assertEquals(ARRAY_TYPE, a.getType());
}
public void testNew13() throws Exception {
TypeCheckResult p = parseAndTypeCheckWithScope(
"/** @constructor */function FooBar(){};" +
"var a = new FooBar();");
Var a = p.scope.getVar("a");
assertTrue(a.getType() instanceof ObjectType);
assertEquals("FooBar", a.getType().toString());
}
public void testNew14() throws Exception {
TypeCheckResult p = parseAndTypeCheckWithScope(
"
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>/** @constructor */var FooBar = function(){};" +
"var a = new FooBar();");
Var a = p.scope.getVar("a");
assertTrue(a.getType() instanceof ObjectType);
assertEquals("FooBar", a.getType().toString());
}
public void testNew15() throws Exception {
TypeCheckResult p = parseAndTypeCheckWithScope(
"var goog = {};" +
"/** @constructor */goog.A = function(){};" +
"var a = new goog.A();");
Var a = p.scope.getVar("a");
assertTrue(a.getType() instanceof ObjectType);
assertEquals("goog.A", a.getType().toString());
}
public void testNew16() throws Exception {
testTypes(
"/** \n" +
" * @param {string} x \n" +
" * @constructor \n" +
" */" +
"function Foo(x) {}" +
"function g() { new Foo(1); }",
"actual parameter 1 of Foo does not match formal parameter\n" +
"found : number\n" +
"required: string");
}
public void testNew17() throws Exception {
testTypes("var goog = {}; goog.x = 3; new goog.x",
"cannot instantiate non-constructor");
}
public void testNew18() throws Exception {
testTypes("var goog = {};" +
"/** @constructor */ goog.F = function() {};" +
"/** @constructor */ goog.G = goog.F;");
}
public void testName1() throws Exception {
assertEquals(VOID_TYPE, testNameNode("undefined"));
}
public void testName2() throws Exception {
assertEquals(OBJECT_FUNCTION_TYPE, testNameNode("Object"));
}
public void testName3() throws Exception {
assertEquals(ARRAY_FUNCTION_TYPE, testNameNode("Array"));
}
public void testName4() throws Exception {
assertEquals(DATE_FUNCTION_TYPE, testNameNode("Date"));
}
public void testName5() throws Exception {
assertEquals(REGEXP_FUNCTION_TYPE, testNameNode("RegExp"));
}
/**
* Type checks a NAME node and retrieve its type.
*/
private JSType testNameNode(String name) {
Node node = Node.newString(Token.NAME, name);
Node parent = new Node(Token.SCRIPT, node);
parent.setInputId(new InputId("code"));
Node externs = new Node(Token.SCRIPT);
externs.setInputId(new InputId("externs"));
Node externAndJsRoot = new Node(Token.BLOCK, externs, parent);
externAndJsRoot.setIsSyntheticBlock(true);
makeTypeCheck().processForTesting(null, parent);
return node.getJSType();
}
public void testBitOperation1() throws Exception {
testTypes("/**@return
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> = new Date();",
"initializing variable\n" +
"found : Date\n" +
"required: Foo");
}
public void testConstructorType2() throws Exception {
testTypes("/**@constructor*/function Foo(){\n" +
"/**@type{Number}*/this.bar = new Number(5);\n" +
"}\n" +
"/**@type{Foo}*/var f = new Foo();\n" +
"/**@type{Number}*/var n = f.bar;");
}
public void testConstructorType3() throws Exception {
// Reverse the declaration order so that we know that Foo is getting set
// even on an out-of-order declaration sequence.
testTypes("/**@type{Foo}*/var f = new Foo();\n" +
"/**@type{Number}*/var n = f.bar;" +
"/**@constructor*/function Foo(){\n" +
"/**@type{Number}*/this.bar = new Number(5);\n" +
"}\n");
}
public void testConstructorType4() throws Exception {
testTypes("/**@constructor*/function Foo(){\n" +
"/**@type{!Number}*/this.bar = new Number(5);\n" +
"}\n" +
"/**@type{!Foo}*/var f = new Foo();\n" +
"/**@type{!String}*/var n = f.bar;",
"initializing variable\n" +
"found : Number\n" +
"required: String");
}
public void testConstructorType5() throws Exception {
testTypes("/**@constructor*/function Foo(){}\n" +
"if (Foo){}\n");
}
public void testConstructorType6() throws Exception {
testTypes("/** @constructor */\n" +
"function bar() {}\n" +
"function _foo() {\n" +
" /** @param {bar} x */\n" +
" function f(x) {}\n" +
"}");
}
public void testConstructorType7() throws Exception {
TypeCheckResult p =
parseAndTypeCheckWithScope("/** @constructor */function A(){};");
JSType type = p.scope.getVar("A").getType();
assertTrue(type instanceof FunctionType);
FunctionType fType = (FunctionType) type;
assertEquals("A", fType.getReferenceName());
}
public void testConstructorType8() throws Exception {
testTypes(
"var ns = {};" +
"ns.create = function() { return function() {}; };" +
"/** @constructor */ ns.Foo = ns.create();" +
"ns.Foo.prototype = {x: 0, y: 0};" +
"/**\n" +
" * @param {ns.Foo} foo\n" +
" * @return {string}\n" +
" */
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> {
testTypes("function g(f) { var x = new f(); x.a = 1; return x; }");
}
public void testUnknownPrototypeChain() throws Exception {
testTypes("/**\n" +
"* @param {Object} co\n" +
" * @return {Object}\n" +
" */\n" +
"function inst(co) {\n" +
" /** @constructor */\n" +
" var c = function() {};\n" +
" c.prototype = co.prototype;\n" +
" return new c;\n" +
"}");
}
public void testNamespacedConstructor() throws Exception {
Node root = parseAndTypeCheck(
"var goog = {};" +
"/** @constructor */ goog.MyClass = function() {};" +
"/** @return {!goog.MyClass} */ " +
"function foo() { return new goog.MyClass(); }");
JSType typeOfFoo = root.getLastChild().getJSType();
assert(typeOfFoo instanceof FunctionType);
JSType retType = ((FunctionType) typeOfFoo).getReturnType();
assert(retType instanceof ObjectType);
assertEquals("goog.MyClass", ((ObjectType) retType).getReferenceName());
}
public void testComplexNamespace() throws Exception {
String js =
"var goog = {};" +
"goog.foo = {};" +
"goog.foo.bar = 5;";
TypeCheckResult p = parseAndTypeCheckWithScope(js);
// goog type in the scope
JSType googScopeType = p.scope.getVar("goog").getType();
assertTrue(googScopeType instanceof ObjectType);
assertTrue("foo property not present on goog type",
((ObjectType) googScopeType).hasProperty("foo"));
assertFalse("bar property present on goog type",
((ObjectType) googScopeType).hasProperty("bar"));
// goog type on the VAR node
Node varNode = p.root.getFirstChild();
assertEquals(Token.VAR, varNode.getType());
JSType googNodeType = varNode.getFirstChild().getJSType();
assertTrue(googNodeType instanceof ObjectType);
// goog scope type and goog type on VAR node must be the same
assertTrue(googScopeType == googNodeType);
// goog type on the left of the GETPROP node (under fist ASSIGN)
Node getpropFoo1 = varNode.getNext().getFirstChild().getFirstChild();
assertEquals(Token.GETPROP, getpropFoo1.getType());
assertEquals("goog", getpropFoo1.getFirstChild().getString());
JSType googGetpropFoo1Type = getpropFoo1.getFirstChild().getJSType();
assertTrue(googGetpropFoo1Type instanceof ObjectType);
// still the same type as the one on the variable
assertTrue(googGetpropFoo1Type == googScopeType);
// the foo property should be defined on goog
JSType googFooType = ((ObjectType) googScopeType).getPropertyType
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>("foo");
assertTrue(googFooType instanceof ObjectType);
// goog type on the left of the GETPROP lower level node
// (under second ASSIGN)
Node getpropFoo2 = varNode.getNext().getNext()
.getFirstChild().getFirstChild().getFirstChild();
assertEquals(Token.GETPROP, getpropFoo2.getType());
assertEquals("goog", getpropFoo2.getFirstChild().getString());
JSType googGetpropFoo2Type = getpropFoo2.getFirstChild().getJSType();
assertTrue(googGetpropFoo2Type instanceof ObjectType);
// still the same type as the one on the variable
assertTrue(googGetpropFoo2Type == googScopeType);
// goog.foo type on the left of the top-level GETPROP node
// (under second ASSIGN)
JSType googFooGetprop2Type = getpropFoo2.getJSType();
assertTrue("goog.foo incorrectly annotated in goog.foo.bar selection",
googFooGetprop2Type instanceof ObjectType);
ObjectType googFooGetprop2ObjectType = (ObjectType) googFooGetprop2Type;
assertFalse("foo property present on goog.foo type",
googFooGetprop2ObjectType.hasProperty("foo"));
assertTrue("bar property not present on goog.foo type",
googFooGetprop2ObjectType.hasProperty("bar"));
assertEquals("bar property on goog.foo type incorrectly inferred",
NUMBER_TYPE, googFooGetprop2ObjectType.getPropertyType("bar"));
}
public void testAddingMethodsUsingPrototypeIdiomSimpleNamespace()
throws Exception {
Node js1Node = parseAndTypeCheck(
"/** @constructor */function A() {}" +
"A.prototype.m1 = 5");
ObjectType instanceType = getInstanceType(js1Node);
assertEquals(NATIVE_PROPERTIES_COUNT + 1,
instanceType.getPropertiesCount());
checkObjectType(instanceType, "m1", NUMBER_TYPE);
}
public void testAddingMethodsUsingPrototypeIdiomComplexNamespace1()
throws Exception {
TypeCheckResult p = parseAndTypeCheckWithScope(
"var goog = {};" +
"goog.A = /** @constructor */function() {};" +
"/** @type number */goog.A.prototype.m1 = 5");
testAddingMethodsUsingPrototypeIdiomComplexNamespace(p);
}
public void testAddingMethodsUsingPrototypeIdiomComplexNamespace2()
throws Exception {
TypeCheckResult p = parseAndTypeCheckWithScope(
"var goog = {};" +
"/** @constructor */goog.A = function() {};" +
"/** @type number */goog.A.prototype.m1 = 5");
testAddingMethodsUsingPrototypeIdiomComplexNamespace(p);
}
private void testAddingMethodsUsingPrototypeIdiomComplexNamespace(
TypeCheckResult p) {
ObjectType goog = (ObjectType) p.scope.getVar("goog").getType();
assertEquals(NATIVE_PROPERTIES_COUNT + 1, goog.getPropertiesCount());
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>");
}
public void testInterfacePropertyNotImplemented2() throws Exception {
testTypes(
"/** @interface */function Int() {};" +
"/** @desc description */Int.prototype.foo = function() {};" +
"/** @interface \n @extends {Int} */function Int2() {};" +
"/** @constructor\n @implements {Int2} */function Foo() {};",
"property foo on interface Int is not implemented by type Foo");
}
public void testStubConstructorImplementingInterface() throws Exception {
// This does not throw a warning for unimplemented property because Foo is
// just a stub.
testTypes(
// externs
"/** @interface */ function Int() {}\n" +
"/** @desc description */Int.prototype.foo = function() {};" +
"/** @constructor \n @implements {Int} */ var Foo;\n",
"", null, false);
}
public void testObjectLiteral() throws Exception {
Node n = parseAndTypeCheck("var a = {m1: 7, m2: 'hello'}");
Node nameNode = n.getFirstChild().getFirstChild();
Node objectNode = nameNode.getFirstChild();
// node extraction
assertEquals(Token.NAME, nameNode.getType());
assertEquals(Token.OBJECTLIT, objectNode.getType());
// value's type
ObjectType objectType =
(ObjectType) objectNode.getJSType();
assertEquals(NUMBER_TYPE, objectType.getPropertyType("m1"));
assertEquals(STRING_TYPE, objectType.getPropertyType("m2"));
// variable's type
assertEquals(objectType, nameNode.getJSType());
}
public void testObjectLiteralDeclaration1() throws Exception {
testTypes(
"var x = {" +
"/** @type {boolean} */ abc: true," +
"/** @type {number} */ 'def': 0," +
"/** @type {string} */ 3: 'fgh'" +
"};");
}
public void testObjectLiteralDeclaration2() throws Exception {
testTypes(
"var x = {" +
" /** @type {boolean} */ abc: true" +
"};" +
"x.abc = 0;",
"assignment to property abc of x\n" +
"found : number\n" +
"required: boolean");
}
public void testObjectLiteralDeclaration3() throws Exception {
testTypes(
"/** @param {{foo: !Function}} x */ function f(x) {}" +
"f({foo: function() {}});");
}
public void testObjectLiteralDeclaration4() throws Exception {
testClosureTypes(
"var x = {" +
" /** @param {boolean} x */ abc: function(x) {}" +
"};" +
"/**\n" +
" * @param {string} x\n" +
" * @suppress {duplicate}\n" +
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>constructor */ u.A = function() {};\n" +
"/**\n* @constructor\n* @extends {u.A}\n*/\nu.A.A = function() {}\n;" +
"/**\n* @constructor\n* @extends {u.A}\n*/\nu.A.B = function() {};\n" +
"var ab = new u.A.B();\n" +
"/** @type {!u.A} */ var a = ab;\n" +
"/** @type {!u.A.A} */ var aa = ab;\n",
"initializing variable\n" +
"found : u.A.B\n" +
"required: u.A.A");
}
public void testResolutionViaRegistry5() throws Exception {
Node n = parseAndTypeCheck("/** @constructor */ u.T = function() {}; u.T");
JSType type = n.getLastChild().getLastChild().getJSType();
assertFalse(type.isUnknownType());
assertTrue(type instanceof FunctionType);
assertEquals("u.T",
((FunctionType) type).getInstanceType().getReferenceName());
}
public void testGatherProperyWithoutAnnotation1() throws Exception {
Node n = parseAndTypeCheck("/** @constructor */ var T = function() {};" +
"/** @type {!T} */var t; t.x; t;");
JSType type = n.getLastChild().getLastChild().getJSType();
assertFalse(type.isUnknownType());
assertTrue(type instanceof ObjectType);
ObjectType objectType = (ObjectType) type;
assertFalse(objectType.hasProperty("x"));
assertEquals(
Lists.newArrayList(objectType),
registry.getTypesWithProperty("x"));
}
public void testGatherProperyWithoutAnnotation2() throws Exception {
TypeCheckResult ns =
parseAndTypeCheckWithScope("/** @type {!Object} */var t; t.x; t;");
Node n = ns.root;
Scope s = ns.scope;
JSType type = n.getLastChild().getLastChild().getJSType();
assertFalse(type.isUnknownType());
assertEquals(type, OBJECT_TYPE);
assertTrue(type instanceof ObjectType);
ObjectType objectType = (ObjectType) type;
assertFalse(objectType.hasProperty("x"));
assertEquals(
Lists.newArrayList(OBJECT_TYPE),
registry.getTypesWithProperty("x"));
}
public void testFunctionMasksVariableBug() throws Exception {
testTypes("var x = 4; var f = function x(b) { return b ? 1 : x(true); };",
"function x masks variable (IE bug)");
}
public void testDfa1() throws Exception {
testTypes("var x = null;\n x = 1;\n /** @type number */ var y = x;");
}
public void testDfa2() throws Exception {
testTypes("function u()
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> getTypedPercent(js), 0.1);
}
public void testGetTypedPercent5() throws Exception {
String js = "/** @enum {number} */ keys = {A: 1,B: 2,C: 3};";
assertEquals(100.0, getTypedPercent(js), 0.1);
}
public void testGetTypedPercent6() throws Exception {
String js = "a = {TRUE: 1, FALSE: 0};";
assertEquals(100.0, getTypedPercent(js), 0.1);
}
private double getTypedPercent(String js) throws Exception {
Node n = compiler.parseTestCode(js);
Node externs = new Node(Token.BLOCK);
Node externAndJsRoot = new Node(Token.BLOCK, externs, n);
externAndJsRoot.setIsSyntheticBlock(true);
TypeCheck t = makeTypeCheck();
t.processForTesting(null, n);
return t.getTypedPercent();
}
private ObjectType getInstanceType(Node js1Node) {
JSType type = js1Node.getFirstChild().getJSType();
assertNotNull(type);
assertTrue(type instanceof FunctionType);
FunctionType functionType = (FunctionType) type;
assertTrue(functionType.isConstructor());
return functionType.getInstanceType();
}
public void testPrototypePropertyReference() throws Exception {
TypeCheckResult p = parseAndTypeCheckWithScope(""
+ "/** @constructor */\n"
+ "function Foo() {}\n"
+ "/** @param {number} a */\n"
+ "Foo.prototype.bar = function(a){};\n"
+ "/** @param {Foo} f */\n"
+ "function baz(f) {\n"
+ " Foo.prototype.bar.call(f, 3);\n"
+ "}");
assertEquals(0, compiler.getErrorCount());
assertEquals(0, compiler.getWarningCount());
assertTrue(p.scope.getVar("Foo").getType() instanceof FunctionType);
FunctionType fooType = (FunctionType) p.scope.getVar("Foo").getType();
assertEquals("function (this:Foo, number): undefined",
fooType.getPrototype().getPropertyType("bar").toString());
}
public void testResolvingNamedTypes() throws Exception {
String js = ""
+ "/** @constructor */\n"
+ "var Foo = function() {}\n"
+ "/** @param {number} a */\n"
+ "Foo.prototype.foo = function(a) {\n"
+ " return this.baz().toString();\n"
+ "};\n"
+ "/** @return {Baz} */\n"
+ "Foo.prototype.baz = function() { return new Baz(); };\n"
+ "/** @constructor\n"
+ " * @extends Foo */\n"
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> < b; ++i) {}}");
// check the type of the add node for i + f
assertEquals(registry.getNativeType(JSTypeNative.NUMBER_TYPE),
n.getFirstChild().getLastChild().getLastChild().getFirstChild()
.getNext().getFirstChild().getJSType());
}
public void testFlowScopeBug2() throws Exception {
Node n = parseAndTypeCheck("/** @constructor */ function Foo() {};\n"
+ "Foo.prototype.hi = false;"
+ "function foo(a, b) {\n"
+ " /** @type Array */"
+ " var arr;"
+ " /** @type number */"
+ " var iter;"
+ " for (iter = 0; iter < arr.length; ++ iter) {"
+ " /** @type Foo */"
+ " var afoo = arr[iter];"
+ " afoo;"
+ " }"
+ "}");
// check the type of afoo when referenced
assertEquals(registry.createNullableType(registry.getType("Foo")),
n.getLastChild().getLastChild().getLastChild().getLastChild()
.getLastChild().getLastChild().getJSType());
}
public void testAddSingletonGetter() {
Node n = parseAndTypeCheck(
"/** @constructor */ function Foo() {};\n" +
"goog.addSingletonGetter(Foo);");
ObjectType o = (ObjectType) n.getFirstChild().getJSType();
assertEquals("function (): Foo",
o.getPropertyType("getInstance").toString());
assertEquals("Foo", o.getPropertyType("instance_").toString());
}
public void testTypeCheckStandaloneAST() throws Exception {
Node n = compiler.parseTestCode("function Foo() { }");
typeCheck(n);
TypedScopeCreator scopeCreator = new TypedScopeCreator(compiler);
Scope topScope = scopeCreator.createScope(n, null);
Node second = compiler.parseTestCode("new Foo");
Node externs = new Node(Token.BLOCK);
Node externAndJsRoot = new Node(Token.BLOCK, externs, second);
externAndJsRoot.setIsSyntheticBlock(true);
new TypeCheck(
compiler,
new SemanticReverseAbstractInterpreter(
compiler.getCodingConvention(), registry),
registry, topScope, scopeCreator, CheckLevel.WARNING, CheckLevel.OFF)
.process(null, second);
assertEquals(1, compiler.getWarningCount());
assertEquals("cannot instantiate non-constructor",
compiler.getWarnings()[0].description);
}
public void testUpdateParameterTypeOnClosure() throws Exception {
testTypes(
"/**\n" +
"* @constructor\n" +
"* @param {*=} opt_value\n" +
"* @return {?}\n" +
"*/\n" +
"function Object(opt_value) {}\n" +
"/**\n" +
"* @constructor
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>());
if (warnings != null) {
assertEquals(warnings.length, compiler.getWarningCount());
JSError[] messages = compiler.getWarnings();
for (int i = 0; i < warnings.length && i < compiler.getWarningCount();
i++) {
assertEquals(warnings[i], messages[i].description);
}
} else {
assertEquals(0, compiler.getWarningCount());
}
}
String suppressMissingProperty(String ... props) {
String result = "function dummy(x) { ";
for (String prop : props) {
result += "x." + prop + " = 3;";
}
return result + "}";
}
private static class TypeCheckResult {
private final Node root;
private final Scope scope;
private TypeCheckResult(Node root, Scope scope) {
this.root = root;
this.scope = scope;
}
}
}
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> Now, generate the priority of nodes by doing a depth-first
// search on the CFG.
priorityCounter = 0;
DiGraphNode<Node, Branch> entry = cfg.getEntry();
prioritizeFromEntryNode(entry);
if (shouldTraverseFunctions) {
// If we're traversing inner functions, we need to rank the
// priority of them too.
for (DiGraphNode<Node, Branch> candidate : cfg.getDirectedGraphNodes()) {
Node value = candidate.getValue();
if (value != null && value.isFunction()) {
Preconditions.checkState(
!nodePriorities.containsKey(candidate) || candidate == entry);
prioritizeFromEntryNode(candidate);
}
}
}
// At this point, all reachable nodes have been given a priority, but
// unreachable nodes have not been given a priority. Put them last.
// Presumably, it doesn't really matter what priority they get, since
// this shouldn't happen in real code.
for (DiGraphNode<Node, Branch> candidate : cfg.getDirectedGraphNodes()) {
if (!nodePriorities.containsKey(candidate)) {
nodePriorities.put(candidate, ++priorityCounter);
}
}
// Again, the implicit return node is always last.
nodePriorities.put(cfg.getImplicitReturn(), ++priorityCounter);
}
/**
* Given an entry node, find all the nodes reachable from that node
* and prioritize them.
*/
private void prioritizeFromEntryNode(DiGraphNode<Node, Branch> entry) {
PriorityQueue<DiGraphNode<Node, Branch>> worklist =
new PriorityQueue<DiGraphNode<Node, Branch>>(10, priorityComparator);
worklist.add(entry);
while (!worklist.isEmpty()) {
DiGraphNode<Node, Branch> current = worklist.remove();
if (nodePriorities.containsKey(current)) {
continue;
}
nodePriorities.put(current, ++priorityCounter);
List<DiGraphNode<Node, Branch>> successors =
cfg.getDirectedSuccNodes(current);
for (DiGraphNode<Node, Branch> candidate : successors) {
worklist.add(candidate);
}
}
}
@Override
public boolean shouldTraverse(
NodeTraversal nodeTraversal, Node n, Node parent) {
astPosition.put(n, astPositionCounter++);
switch (n.getType()) {
case Token.FUNCTION:
if (shouldTraverseFunctions || n == cfg.getEntry().getValue()) {
exceptionHandler.push(n);
return true;
}
return false;
case Token.TRY:
exceptionHandler.push(n);
return true;
}
/*
* We are going to stop the traversal depending on what the node's parent
* is.
*
* We are only interested in adding edges between nodes that change control
* flow. The most obvious ones are
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> loops and IF-ELSE's. A statement
* transfers control to its next sibling.
*
* In case of an expression tree, there is no control flow within the tree
* even when there are short circuited operators and conditionals. When we
* are doing data flow analysis, we will simply synthesize lattices up the
* expression tree by finding the meet at each expression node.
*
* For example: within a Token.SWITCH, the expression in question does not
* change the control flow and need not to be considered.
*/
if (parent != null) {
switch (parent.getType()) {
case Token.FOR:
// Only traverse the body of the for loop.
return n == parent.getLastChild();
// Skip the conditions.
case Token.IF:
case Token.WHILE:
case Token.WITH:
return n != parent.getFirstChild();
case Token.DO:
return n != parent.getFirstChild().getNext();
// Only traverse the body of the cases
case Token.SWITCH:
case Token.CASE:
case Token.CATCH:
case Token.LABEL:
return n != parent.getFirstChild();
case Token.FUNCTION:
return n == parent.getFirstChild().getNext().getNext();
case Token.CONTINUE:
case Token.BREAK:
case Token.EXPR_RESULT:
case Token.VAR:
case Token.RETURN:
case Token.THROW:
return false;
case Token.TRY:
/* Just before we are about to visit the second child of the TRY node,
* we know that we will be visiting either the CATCH or the FINALLY.
* In other words, we know that the post order traversal of the TRY
* block has been finished, no more exceptions can be caught by the
* handler at this TRY block and should be taken out of the stack.
*/
if (n == parent.getFirstChild().getNext()) {
Preconditions.checkState(exceptionHandler.peek() == parent);
exceptionHandler.pop();
}
}
}
return true;
}
@Override
public void visit(NodeTraversal t, Node n, Node parent) {
switch (n.getType()) {
case Token.IF:
handleIf(n);
return;
case Token.WHILE:
handleWhile(n);
return;
case Token.DO:
handleDo(n);
return;
case Token.FOR:
handleFor(n);
return;
case Token.SWITCH:
handleSwitch(n);
return;
case Token.CASE:
handleCase(n);
return;
case Token.DEFAULT_CASE:
handleDefault(n);
return;
case Token.BLOCK:
case Token.SCRIPT:
handleStmtList(n);
return;
case Token.FUNCTION:
handleFunction(n);
return;
case Token.EXPR_RESULT:
handleExpr(n);
return;
case Token.THROW:
handleThrow(n
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>node, node.getFirstChild());
}
private void handleDefault(Node node) {
// Directly goes to the body. It should not transfer to the next case.
createEdge(node, Branch.UNCOND, node.getFirstChild());
}
private void handleWith(Node node) {
// Directly goes to the body. It should not transfer to the next case.
createEdge(node, Branch.UNCOND, node.getLastChild());
connectToPossibleExceptionHandler(node, node.getFirstChild());
}
private void handleStmtList(Node node) {
Node parent = node.getParent();
// Special case, don't add a block of empty CATCH block to the graph.
if (node.isBlock() && parent != null &&
parent.isTry() &&
NodeUtil.getCatchBlock(parent) == node &&
!NodeUtil.hasCatchHandler(node)) {
return;
}
// A block transfer control to its first child if it is not empty.
Node child = node.getFirstChild();
// Function declarations are skipped since control doesn't go into that
// function (unless it is called)
while (child != null && child.isFunction()) {
child = child.getNext();
}
if (child != null) {
createEdge(node, Branch.UNCOND, computeFallThrough(child));
} else {
createEdge(node, Branch.UNCOND, computeFollowNode(node, this));
}
// Synthetic blocks
if (parent != null) {
switch (parent.getType()) {
case Token.DEFAULT_CASE:
case Token.CASE:
case Token.TRY:
break;
default:
if (node.isBlock() && node.isSyntheticBlock()) {
createEdge(node, Branch.SYN_BLOCK, computeFollowNode(node, this));
}
break;
}
}
}
private void handleFunction(Node node) {
// A block transfer control to its first child if it is not empty.
Preconditions.checkState(node.getChildCount() >= 3);
createEdge(node, Branch.UNCOND,
computeFallThrough(node.getFirstChild().getNext().getNext()));
Preconditions.checkState(exceptionHandler.peek() == node);
exceptionHandler.pop();
}
private void handleExpr(Node node) {
createEdge(node, Branch.UNCOND, computeFollowNode(node, this));
connectToPossibleExceptionHandler(node, node);
}
private void handleThrow(Node node) {
connectToPossibleExceptionHandler(node, node);
}
private void handleTry(Node node) {
createEdge(node, Branch.UNCOND, node.getFirstChild());
}
private void handleCatch(Node node) {
createEdge(node, Branch.UNCOND, node.getLastChild());
}
private void handleBreak(Node node) {
String label = null;
// See if it is a break with label.
if (node
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> node.getParent();
if (parent == null || parent.isFunction() ||
(cfa != null && node == cfa.root)) {
return null;
}
// If we are just before a IF/WHILE/DO/FOR:
switch (parent.getType()) {
// The follow() of any of the path from IF would be what follows IF.
case Token.IF:
return computeFollowNode(fromNode, parent, cfa);
case Token.CASE:
case Token.DEFAULT_CASE:
// After the body of a CASE, the control goes to the body of the next
// case, without having to go to the case condition.
if (parent.getNext() != null) {
if (parent.getNext().isCase()) {
return parent.getNext().getFirstChild().getNext();
} else if (parent.getNext().isDefaultCase()) {
return parent.getNext().getFirstChild();
} else {
Preconditions.checkState(false, "Not reachable");
}
} else {
return computeFollowNode(fromNode, parent, cfa);
}
break;
case Token.FOR:
if (NodeUtil.isForIn(parent)) {
return parent;
} else {
return parent.getFirstChild().getNext().getNext();
}
case Token.WHILE:
case Token.DO:
return parent;
case Token.TRY:
// If we are coming out of the TRY block...
if (parent.getFirstChild() == node) {
if (NodeUtil.hasFinally(parent)) { // and have FINALLY block.
return computeFallThrough(parent.getLastChild());
} else { // and have no FINALLY.
return computeFollowNode(fromNode, parent, cfa);
}
// CATCH block.
} else if (NodeUtil.getCatchBlock(parent) == node){
if (NodeUtil.hasFinally(parent)) { // and have FINALLY block.
return computeFallThrough(node.getNext());
} else {
return computeFollowNode(fromNode, parent, cfa);
}
// If we are coming out of the FINALLY block...
} else if (parent.getLastChild() == node){
if (cfa != null) {
for (Node finallyNode : cfa.finallyMap.get(parent)) {
cfa.createEdge(fromNode, Branch.UNCOND, finallyNode);
}
}
return computeFollowNode(fromNode, parent, cfa);
}
}
// Now that we are done with the special cases follow should be its
// immediate sibling, unless its sibling is a function
Node nextSibling = node.getNext();
// Skip function declarations because control doesn't get pass into it.
while (nextSibling != null && nextSibling.isFunction()) {
nextSibling = nextSibling.getNext();
}
if (nextSibling != null) {
return computeFallThrough(nextSibling);
} else {
// If there are no more
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> siblings, control is transferred up the AST.
return computeFollowNode(fromNode, parent, cfa);
}
}
/**
* Computes the destination node of n when we want to fallthrough into the
* subtree of n. We don't always create a CFG edge into n itself because of
* DOs and FORs.
*/
static Node computeFallThrough(Node n) {
switch (n.getType()) {
case Token.DO:
return computeFallThrough(n.getFirstChild());
case Token.FOR:
if (NodeUtil.isForIn(n)) {
return n.getFirstChild().getNext();
}
return computeFallThrough(n.getFirstChild());
case Token.LABEL:
return computeFallThrough(n.getLastChild());
default:
return n;
}
}
/**
* Connects the two nodes in the control flow graph.
*
* @param fromNode Source.
* @param toNode Destination.
*/
private void createEdge(Node fromNode, ControlFlowGraph.Branch branch,
Node toNode) {
cfg.createNode(fromNode);
cfg.createNode(toNode);
cfg.connectIfNotFound(fromNode, branch, toNode);
}
/**
* Connects cfgNode to the proper CATCH block if target subtree might throw
* an exception. If there are FINALLY blocks reached before a CATCH, it will
* make the corresponding entry in finallyMap.
*/
private void connectToPossibleExceptionHandler(Node cfgNode, Node target) {
if (mayThrowException(target) && !exceptionHandler.isEmpty()) {
Node lastJump = cfgNode;
for (Node handler : exceptionHandler) {
if (handler.isFunction()) {
return;
}
Preconditions.checkState(handler.isTry());
Node catchBlock = NodeUtil.getCatchBlock(handler);
if (!NodeUtil.hasCatchHandler(catchBlock)) { // No catch but a FINALLY.
if (lastJump == cfgNode) {
createEdge(cfgNode, Branch.ON_EX, handler.getLastChild());
} else {
finallyMap.put(lastJump, handler.getLastChild());
}
} else { // Has a catch.
if (lastJump == cfgNode) {
createEdge(cfgNode, Branch.ON_EX, catchBlock);
return;
} else {
finallyMap.put(lastJump, catchBlock);
}
}
lastJump = handler;
}
}
}
/**
* Get the next sibling (including itself) of one of the given types.
*/
private static Node getNextSiblingOfType(Node first, int ... types) {
for (Node c = first; c != null; c = c.getNext()) {
for (int type : types) {
if (c.getType() == type) {
return c;
}
}
}
return null;
}
/**
* Checks if target is actually the break target
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> of labeled continue. The
* label can be null if it is an unlabeled break.
*/
public static boolean isBreakTarget(Node target, String label) {
return isBreakStructure(target, label != null) &&
matchLabel(target.getParent(), label);
}
/**
* Checks if target is actually the continue target of labeled continue. The
* label can be null if it is an unlabeled continue.
*/
private static boolean isContinueTarget(
Node target, Node parent, String label) {
return isContinueStructure(target) && matchLabel(parent, label);
}
/**
* Check if label is actually referencing the target control structure. If
* label is null, it always returns true.
*/
private static boolean matchLabel(Node target, String label) {
if (label == null) {
return true;
}
while (target.isLabel()) {
if (target.getFirstChild().getString().equals(label)) {
return true;
}
target = target.getParent();
}
return false;
}
/**
* Determines if the subtree might throw an exception.
*/
public static boolean mayThrowException(Node n) {
switch (n.getType()) {
case Token.CALL:
case Token.GETPROP:
case Token.GETELEM:
case Token.THROW:
case Token.NEW:
case Token.ASSIGN:
case Token.INC:
case Token.DEC:
case Token.INSTANCEOF:
return true;
case Token.FUNCTION:
return false;
}
for (Node c = n.getFirstChild(); c != null; c = c.getNext()) {
if (!ControlFlowGraph.isEnteringNewCfgNode(c) && mayThrowException(c)) {
return true;
}
}
return false;
}
/**
* Determines whether the given node can be terminated with a BREAK node.
*/
static boolean isBreakStructure(Node n, boolean labeled) {
switch (n.getType()) {
case Token.FOR:
case Token.DO:
case Token.WHILE:
case Token.SWITCH:
return true;
case Token.BLOCK:
case Token.IF:
case Token.TRY:
return labeled;
default:
return false;
}
}
/**
* Determines whether the given node can be advanced with a CONTINUE node.
*/
static boolean isContinueStructure(Node n) {
switch (n.getType()) {
case Token.FOR:
case Token.DO:
case Token.WHILE:
return true;
default:
return false;
}
}
/**
* Get the TRY block with a CATCH that would be run if n throws an exception.
* @return The CATCH node or null if it there isn't a CATCH before the
* the function terminates.
*/
static Node getExceptionHandler(Node n) {
for (Node cur = n;
!cur.isScript()
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>(interfaceInstance.getReferenceName(), type);
}
/**
* Returns a collection of types that directly implement {@code
* interfaceInstance}. Subtypes of implementing types are not guaranteed to
* be returned. {@code interfaceInstance} must be an ObjectType for the
* instance of the interface.
*/
public Collection<FunctionType> getDirectImplementors(
ObjectType interfaceInstance) {
return interfaceToImplementors.get(interfaceInstance.getReferenceName());
}
/**
* Records declared global type names. This makes resolution faster
* and more robust in the common case.
*
* @param name The name of the type to be recorded.
* @param t The actual type being associated with the name.
* @return True if this name is not already defined, false otherwise.
*/
public boolean declareType(String name, JSType t) {
if (namesToTypes.containsKey(name)) {
return false;
}
register(t, name);
return true;
}
/**
* Overrides a declared global type name. Throws an exception if this
* type name hasn't been declared yet.
*/
public void overwriteDeclaredType(String name, JSType t) {
Preconditions.checkState(namesToTypes.containsKey(name));
register(t, name);
}
/**
* Records a forward-declared type name. We will not emit errors if this
* type name never resolves to anything.
*/
public void forwardDeclareType(String name) {
forwardDeclaredTypes.add(name);
}
/**
* Whether this is a forward-declared type name.
*/
public boolean isForwardDeclaredType(String name) {
return forwardDeclaredTypes.contains(name);
}
/** Determines whether the given JS package exists. */
public boolean hasNamespace(String name) {
return namespaces.contains(name);
}
/**
* Looks up a type by name.
*
* @param jsTypeName The name string.
* @return the corresponding JSType object or {@code null} it cannot be found
*/
public JSType getType(String jsTypeName) {
// TODO(user): Push every local type name out of namesToTypes so that
// NamedType#resolve is correct.
TemplateType templateType = templateTypes.get(jsTypeName);
if (templateType != null) {
return templateType;
}
return namesToTypes.get(jsTypeName);
}
public JSType getNativeType(JSTypeNative typeId) {
return nativeTypes[typeId.ordinal()];
}
public ObjectType getNativeObjectType(JSTypeNative typeId) {
return (ObjectType) getNativeType(typeId);
}
public FunctionType getNativeFunctionType(JSTypeNative typeId) {
return (FunctionType) getNativeType(typeId);
}
/**
* Looks up a type by name. To allow for forward references to types, an
* unrecognized string has to be bound to a NamedType object that will be
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> * resolved later.
*
* @param scope A scope for doing type name resolution.
* @param jsTypeName The name string.
* @param sourceName The name of the source file where this reference appears.
* @param lineno The line number of the reference.
* @return a NamedType if the string argument is not one of the known types,
* otherwise the corresponding JSType object.
*/
public JSType getType(StaticScope<JSType> scope, String jsTypeName,
String sourceName, int lineno, int charno) {
JSType type = getType(jsTypeName);
if (type == null) {
// TODO(user): Each instance should support named type creation using
// interning.
NamedType namedType =
new NamedType(this, jsTypeName, sourceName, lineno, charno);
unresolvedNamedTypes.put(scope, namedType);
type = namedType;
}
return type;
}
/**
* Flushes out the current resolved and unresolved Named Types from
* the type registry. This is intended to be used ONLY before a
* compile is run.
*/
public void clearNamedTypes() {
resolvedNamedTypes.clear();
unresolvedNamedTypes.clear();
}
/**
* Resolve all the unresolved types in the given scope.
*/
public void resolveTypesInScope(StaticScope<JSType> scope) {
for (NamedType type : unresolvedNamedTypes.get(scope)) {
type.resolve(reporter, scope);
}
resolvedNamedTypes.putAll(scope, unresolvedNamedTypes.removeAll(scope));
if (scope != null && scope.getParentScope() == null) {
// By default, the global "this" type is just an anonymous object.
// If the user has defined a Window type, make the Window the
// implicit prototype of "this".
PrototypeObjectType globalThis = (PrototypeObjectType) getNativeType(
JSTypeNative.GLOBAL_THIS);
JSType windowType = getType("Window");
if (globalThis.isUnknownType()) {
ObjectType windowObjType = ObjectType.cast(windowType);
if (windowObjType != null) {
globalThis.setImplicitPrototype(windowObjType);
} else {
globalThis.setImplicitPrototype(
getNativeObjectType(JSTypeNative.OBJECT_TYPE));
}
}
}
}
/**
* Creates a type representing optional values of the given type.
* @return the union of the type and the void type
*/
public JSType createOptionalType(JSType type) {
if (type instanceof UnknownType || type.isAllType()) {
return type;
} else {
return createUnionType(type, getNativeType(JSTypeNative.VOID_TYPE));
}
}
/**
* Creates a type representing nullable values of the given type.
* @return the union of the type and the Null type
*/
public JSType createDefaultObjectUnion(JSType type
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> returnType), null,
null, true, false);
}
/**
* Creates an interface function type.
* @param name the function's name
* @param source the node defining this function. Its type
* ({@link Node#getType()}) must be {@link Token#FUNCTION}.
*/
public FunctionType createInterfaceType(String name, Node source) {
return FunctionType.forInterface(this, name, source);
}
/**
* Creates a parameterized type.
*/
public ParameterizedType createParameterizedType(
ObjectType objectType, JSType parameterType) {
return new ParameterizedType(this, objectType, parameterType);
}
/**
* Creates a named type.
*/
@VisibleForTesting
public JSType createNamedType(String reference,
String sourceName, int lineno, int charno) {
return new NamedType(this, reference, sourceName, lineno, charno);
}
/**
* Identifies the name of a typedef or enum before we actually declare it.
*/
public void identifyNonNullableName(String name) {
Preconditions.checkNotNull(name);
nonNullableTypeNames.add(name);
}
/**
* Creates a JSType from the nodes representing a type.
* @param n The node with type info.
* @param sourceName The source file name.
* @param scope A scope for doing type name lookups.
*/
public JSType createFromTypeNodes(Node n, String sourceName,
StaticScope<JSType> scope) {
if (resolveMode == ResolveMode.LAZY_EXPRESSIONS) {
// If the type expression doesn't contain any names, just
// resolve it anyway.
boolean hasNames = hasTypeName(n);
if (hasNames) {
return new UnresolvedTypeExpression(this, n, sourceName);
}
}
return createFromTypeNodesInternal(n, sourceName, scope);
}
private boolean hasTypeName(Node n) {
if (n.getType() == Token.STRING) {
return true;
}
for (Node child = n.getFirstChild();
child != null; child = child.getNext()) {
if (hasTypeName(child)) {
return true;
}
}
return false;
}
/** @see #createFromTypeNodes(Node, String, StaticScope) */
private JSType createFromTypeNodesInternal(Node n, String sourceName,
StaticScope<JSType> scope) {
switch (n.getType()) {
case Token.LC: // Record type.
return createRecordTypeFromNodes(
n.getFirstChild(), sourceName, scope);
case Token.BANG: // Not nullable
return createFromTypeNodesInternal(
n.getFirstChild(), sourceName, scope)
.restrictByNotNullOrUndefined();
case Token.QMARK: // Nullable or unknown
Node firstChild = n.getFirstChild();
if (firstChild == null) {
return getNativeType(
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>UNKNOWN_TYPE);
}
return createDefaultObjectUnion(
createFromTypeNodesInternal(
firstChild, sourceName, scope));
case Token.EQUALS: // Optional
return createOptionalType(
createFromTypeNodesInternal(
n.getFirstChild(), sourceName, scope));
case Token.ELLIPSIS: // Var args
return createOptionalType(
createFromTypeNodesInternal(
n.getFirstChild(), sourceName, scope));
case Token.STAR: // The AllType
return getNativeType(ALL_TYPE);
case Token.LB: // Array type
// TODO(nicksantos): Enforce membership restrictions on the Array.
return getNativeType(ARRAY_TYPE);
case Token.PIPE: // Union type
UnionTypeBuilder builder = new UnionTypeBuilder(this);
for (Node child = n.getFirstChild(); child != null;
child = child.getNext()) {
builder.addAlternate(
createFromTypeNodesInternal(child, sourceName, scope));
}
return builder.build();
case Token.EMPTY: // When the return value of a function is not specified
return getNativeType(UNKNOWN_TYPE);
case Token.VOID: // Only allowed in the return value of a function.
return getNativeType(VOID_TYPE);
case Token.STRING:
JSType namedType = getType(scope, n.getString(), sourceName,
n.getLineno(), n.getCharno());
if (resolveMode != ResolveMode.LAZY_NAMES) {
namedType = namedType.resolveInternal(reporter, scope);
}
if ((namedType instanceof ObjectType) &&
!(nonNullableTypeNames.contains(n.getString()))) {
Node typeList = n.getFirstChild();
if (typeList != null &&
("Array".equals(n.getString()) ||
"Object".equals(n.getString()))) {
JSType parameterType =
createFromTypeNodesInternal(
typeList.getLastChild(), sourceName, scope);
namedType = new ParameterizedType(
this, (ObjectType) namedType, parameterType);
if (typeList.hasMoreThanOneChild()) {
JSType indexType =
createFromTypeNodesInternal(
typeList.getFirstChild(), sourceName, scope);
namedType = new IndexedType(
this, (ObjectType) namedType, indexType);
}
}
return createDefaultObjectUnion(namedType);
} else {
return namedType;
}
case Token.FUNCTION:
ObjectType thisType = null;
boolean isConstructor = false;
Node current = n.getFirstChild();
if (current.getType() == Token.THIS ||
current.getType() == Token.NEW) {
Node contextNode = current.getFirstChild();
thisType =
ObjectType.cast(
createFromTypeNodesInternal(
contextNode, sourceName, scope)
.restrictByNotNullOrUndefined());
if (thisType == null) {
reporter.warning(
ScriptRuntime.
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>getMessage0(
current.getType() == Token.THIS ?
"msg.jsdoc.function.thisnotobject" :
"msg.jsdoc.function.newnotobject"),
sourceName,
contextNode.getLineno(), contextNode.getCharno());
}
isConstructor = current.getType() == Token.NEW;
current = current.getNext();
}
FunctionParamBuilder paramBuilder = new FunctionParamBuilder(this);
if (current.getType() == Token.PARAM_LIST) {
Node args = current.getFirstChild();
for (Node arg = current.getFirstChild(); arg != null;
arg = arg.getNext()) {
if (arg.getType() == Token.ELLIPSIS) {
if (arg.getChildCount() == 0) {
paramBuilder.addVarArgs(getNativeType(UNKNOWN_TYPE));
} else {
paramBuilder.addVarArgs(
createFromTypeNodesInternal(
arg.getFirstChild(), sourceName, scope));
}
} else {
JSType type = createFromTypeNodesInternal(
arg, sourceName, scope);
if (arg.getType() == Token.EQUALS) {
boolean addSuccess = paramBuilder.addOptionalParams(type);
if (!addSuccess) {
reporter.warning(
ScriptRuntime.getMessage0("msg.jsdoc.function.varargs"),
sourceName, arg.getLineno(), arg.getCharno());
}
} else {
paramBuilder.addRequiredParams(type);
}
}
}
current = current.getNext();
}
JSType returnType =
createFromTypeNodesInternal(current, sourceName, scope);
return new FunctionBuilder(this)
.withParams(paramBuilder)
.withReturnType(returnType)
.withTypeOfThis(thisType)
.setIsConstructor(isConstructor)
.build();
}
throw new IllegalStateException(
"Unexpected node in type expression: " + n.toString());
}
/**
* Creates a RecordType from the nodes representing said record type.
* @param n The node with type info.
* @param sourceName The source file name.
* @param scope A scope for doing type name lookups.
*/
private JSType createRecordTypeFromNodes(Node n, String sourceName,
StaticScope<JSType> scope) {
RecordTypeBuilder builder = new RecordTypeBuilder(this);
// For each of the fields in the record type.
for (Node fieldTypeNode = n.getFirstChild();
fieldTypeNode != null;
fieldTypeNode = fieldTypeNode.getNext()) {
// Get the property's name.
Node fieldNameNode = fieldTypeNode;
boolean hasType = false;
if (fieldTypeNode.getType() == Token.COLON) {
fieldNameNode = fieldTypeNode.getFirstChild();
hasType = true;
}
String fieldName = fieldNameNode.getString();
// TODO(user): Move this into the lexer/parser.
// Remove the string
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> literal characters around a field name,
// if any.
if (fieldName.startsWith("'") || fieldName.startsWith("\"")) {
fieldName = fieldName.substring(1, fieldName.length() - 1);
}
// Get the property's type.
JSType fieldType = null;
if (hasType) {
// We have a declared type.
fieldType = createFromTypeNodesInternal(
fieldTypeNode.getLastChild(), sourceName, scope);
} else {
// Otherwise, the type is UNKNOWN.
fieldType = getNativeType(JSTypeNative.UNKNOWN_TYPE);
}
// Add the property to the record.
if (builder.addProperty(fieldName, fieldType, fieldNameNode) == null) {
// Duplicate field name, warning and skip
reporter.warning(
"Duplicate record field " + fieldName,
sourceName,
n.getLineno(), fieldNameNode.getCharno());
}
}
return builder.build();
}
/**
* Sets the template type name.
*/
public void setTemplateTypeNames(List<String> names) {
Preconditions.checkNotNull(names);
for (String name : names) {
templateTypes.put(name, new TemplateType(this, name));
}
}
/**
* Clears the template type name.
*/
public void clearTemplateTypeNames() {
templateTypes.clear();
}
}
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>/*
*
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Rhino code, released
* May 6, 1999.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1997-1999
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Bob Jervis
* Google Inc.
*
* Alternatively, the contents of this file may be used under the terms of
* the GNU General Public License Version 2 or later (the "GPL"), in which
* case the provisions of the GPL are applicable instead of those above. If
* you wish to allow use of your version of this file only under the terms of
* the GPL and not to allow others to use your version of this file under the
* MPL, indicate your decision by deleting the provisions above and replacing
* them with the notice and other provisions required by the GPL. If you do
* not delete the provisions above, a recipient may use your version of this
* file under either the MPL or the GPL.
*
* ***** END LICENSE BLOCK ***** */
package com.google.javascript.rhino;
import com.google.javascript.rhino.JSDocInfo.Visibility;
import com.google.javascript.rhino.jstype.StaticSourceFile;
import java.util.List;
import java.util.Set;
/**
* A builder for {@link JSDocInfo} objects. This builder abstracts the
* construction process of {@link JSDocInfo} objects whilst minimizing the
* number of instances of {@link JSDocInfo} objects. It provides early
* incompatibility detection among properties stored on the {@code JSDocInfo}
* object being created.
*
*/
final public class JSDocInfoBuilder {
// the current JSDoc which is being populated
private JSDocInfo currentInfo;
// whether the current JSDocInfo has valuable information
private boolean populated = false;
// whether to include the documentation itself when parsing the JsDoc
private boolean parseDocumentation = false;
// the current marker, if any.
private JSDocInfo.Marker currentMarker = null;
public JSDocInfoBuilder(boolean
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> parseDocumentation) {
this.currentInfo = new JSDocInfo(parseDocumentation);
this.parseDocumentation = parseDocumentation;
}
/**
* Sets the original JSDoc comment string. This is a no-op if the builder
* isn't configured to record documentation.
*/
public void recordOriginalCommentString(String sourceComment) {
if (parseDocumentation) {
currentInfo.setOriginalCommentString(sourceComment);
}
}
public boolean shouldParseDocumentation() {
return parseDocumentation;
}
/**
* Returns whether this builder is populated with information that can be
* used to {@link #build} a {@link JSDocInfo} object.
*/
public boolean isPopulated() {
return populated;
}
/**
* Returns whether this builder is populated with information that can be
* used to {@link #build} a {@link JSDocInfo} object that has a
* fileoverview tag.
*/
public boolean isPopulatedWithFileOverview() {
return isPopulated() &&
(currentInfo.hasFileOverview() || currentInfo.isExterns() ||
currentInfo.isNoCompile());
}
/**
* Returns whether this builder recorded a description.
*/
public boolean isDescriptionRecorded() {
return currentInfo.getDescription() != null;
}
/**
* Builds a {@link JSDocInfo} object based on the populated information and
* returns it. Once this method is called, the builder can be reused to build
* another {@link JSDocInfo} object.
*
* @param associatedNode The source node containing the JSDoc.
* @return a {@link JSDocInfo} object populated with the values given to this
* builder. If no value was populated, this method simply returns
* {@code null}
*/
public JSDocInfo build(Node associatedNode) {
if (populated) {
JSDocInfo built = currentInfo;
built.setAssociatedNode(associatedNode);
populateDefaults(built);
populated = false;
currentInfo = new JSDocInfo(this.parseDocumentation);
return built;
} else {
return null;
}
}
/** Generate defaults when certain parameters are not specified. */
private static void populateDefaults(JSDocInfo info) {
if (info.getVisibility() == null) {
info.setVisibility(Visibility.INHERITED);
}
}
/**
* Adds a marker to the current JSDocInfo and populates the marker with the
* annotation information.
*/
public void markAnnotation(String annotation, int lineno, int charno) {
JSDocInfo.Marker marker = currentInfo.addMarker();
if (marker != null) {
JSDocInfo.TrimmedStringPosition position =
new JSDocInfo.TrimmedStringPosition();
position.setItem(annotation);
position.setPositionInformation(lineno, charno, lineno,
charno
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> + annotation.length());
marker.setAnnotation(position);
populated = true;
}
currentMarker = marker;
}
/**
* Adds a textual block to the current marker.
*/
public void markText(String text, int startLineno, int startCharno,
int endLineno, int endCharno) {
if (currentMarker != null) {
JSDocInfo.StringPosition position = new JSDocInfo.StringPosition();
position.setItem(text);
position.setPositionInformation(startLineno, startCharno,
endLineno, endCharno);
currentMarker.setDescription(position);
}
}
/**
* Adds a type declaration to the current marker.
*/
public void markTypeNode(Node typeNode, int lineno, int startCharno,
int endLineno, int endCharno, boolean hasLC) {
if (currentMarker != null) {
JSDocInfo.TypePosition position = new JSDocInfo.TypePosition();
position.setItem(typeNode);
position.setHasBrackets(hasLC);
position.setPositionInformation(lineno, startCharno,
endLineno, endCharno);
currentMarker.setType(position);
}
}
/**
* Adds a name declaration to the current marker.
* @deprecated Use #markName(String, StaticSourceFile, int, int)
*/
@Deprecated
public void markName(String name, int lineno, int charno) {
markName(name, null, lineno, charno);
}
/**
* Adds a name declaration to the current marker.
*/
public void markName(String name, StaticSourceFile file,
int lineno, int charno) {
if (currentMarker != null) {
// Record the name as both a SourcePosition<String> and a
// SourcePosition<Node>. The <String> form is deprecated,
// because <Node> is more consistent with how other name
// references are handled (see #markTypeNode)
//
// TODO(nicksantos): Remove all uses of the Name position
// and replace them with the NameNode position.
JSDocInfo.TrimmedStringPosition position =
new JSDocInfo.TrimmedStringPosition();
position.setItem(name);
position.setPositionInformation(lineno, charno,
lineno, charno + name.length());
currentMarker.setName(position);
SourcePosition<Node> nodePos =
new JSDocInfo.NamePosition();
Node node = Node.newString(Token.NAME, name, lineno, charno);
node.setLength(name.length());
node.setStaticSourceFile(file);
nodePos.setItem(node);
nodePos.setPositionInformation(lineno, charno,
lineno, charno + name.length());
currentMarker.setNameNode(nodePos);
}
}
/**
* Records a block-level description.
*
* @
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> currentInfo.setLendsName(name);
populated = true;
return true;
} else {
return false;
}
}
/**
* Whether the current doc info has other type tags, like
* {@code @param} or {@code @return} or {@code @type} or etc.
*/
private boolean hasAnyTypeRelatedTags() {
return currentInfo.isConstructor() ||
currentInfo.isInterface() ||
currentInfo.getParameterCount() > 0 ||
currentInfo.hasReturnType() ||
currentInfo.hasBaseType() ||
currentInfo.getExtendedInterfacesCount() > 0 ||
currentInfo.getLendsName() != null ||
currentInfo.hasThisType() ||
hasAnySingletonTypeTags();
}
/**
* Whether the current doc info has any of the singleton type
* tags that may not appear with other type tags, like
* {@code @type} or {@code @typedef}.
*/
private boolean hasAnySingletonTypeTags() {
return currentInfo.hasType() ||
currentInfo.hasTypedefType() ||
currentInfo.hasEnumParameterType();
}
/**
* Whether the current doc info has any of the singleton type
* tags that may not appear with other type tags, like
* {@code @type} or {@code @typedef}.
*/
private boolean hasAnySingletonSideEffectTags() {
return currentInfo.isNoSideEffects() ||
currentInfo.hasModifies();
}
}
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> super.connect(srcValue, edgeValue, null);
}
/**
* Gets a comparator for the nodes. The default implementation returns
* {@code null}. See {@link ControlFlowGraph#getOptionalNodeComparator}.
* @param isForward Whether the comparator sorts the nodes in the direction of
* the flow.
* @return a comparator or null (in particular, if not overridden)
*/
public Comparator<DiGraphNode<N, Branch>> getOptionalNodeComparator(
boolean isForward) {
return null;
}
/**
* The edge object for the control flow graph.
*/
public static enum Branch {
/** Edge is taken if the condition is true. */
ON_TRUE,
/** Edge is taken if the condition is false. */
ON_FALSE,
/** Unconditional branch. */
UNCOND,
/** Exception related. */
ON_EX,
/** Possible folded-away template */
SYN_BLOCK;
public boolean isConditional() {
return this == ON_TRUE || this == ON_FALSE;
}
}
/**
* Abstract callback to visit a control flow graph node without going into
* subtrees of the node that is also represented by another control flow graph
* node.
*
* <p>For example, traversing an IF node as root will visit the two subtree
* pointed by the {@link ControlFlowGraph.Branch#ON_TRUE} and
* {@link ControlFlowGraph.Branch#ON_FALSE} edge.
*/
public abstract static class AbstractCfgNodeTraversalCallback implements
Callback {
@Override
public final boolean shouldTraverse(NodeTraversal nodeTraversal, Node n,
Node parent) {
if (parent == null) {
return true;
}
return !isEnteringNewCfgNode(n);
}
}
/**
* @return True if n should be represented by a new CFG node in the control
* flow graph.
*/
public static boolean isEnteringNewCfgNode(Node n) {
Node parent = n.getParent();
switch (parent.getType()) {
case Token.BLOCK:
case Token.SCRIPT:
case Token.TRY:
return true;
case Token.FUNCTION:
// A function node represents the start of a function where the name
// is bleed into the local scope and parameters has been assigned
// to the formal argument names. The node includes the name of the
// function and the LP list since we assume the whole set up process
// is atomic without change in control flow. The next change of
// control is going into the function's body represent by the second
// child.
return n != parent.getFirstChild().getNext();
case Token.WHILE:
case Token.DO:
case Token.IF:
// Theses control structure is represented by its node that holds the
// condition. Each of them is a branch node based on its condition.
return NodeUtil.getConditionExpression(parent) != n;
case Token.FOR:
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>ed at the top of the lattice by the
* {@link AllType} and at the bottom by the {@link NoType}.<p>
*
*/
public abstract class JSType implements Serializable {
private static final long serialVersionUID = 1L;
private boolean resolved = false;
private JSType resolveResult = null;
private boolean inTemplatedCheckVisit = false;
public static final String UNKNOWN_NAME =
"Unknown class name";
public static final String NOT_A_CLASS =
"Not declared as a constructor";
public static final String NOT_A_TYPE =
"Not declared as a type name";
public static final String EMPTY_TYPE_COMPONENT =
"Named type with empty name component";
/**
* Total ordering on types based on their textual representation.
* This is used to have a deterministic output of the toString
* method of the union type since this output is used in tests.
*/
static final Comparator<JSType> ALPHA = new Comparator<JSType>() {
@Override
public int compare(JSType t1, JSType t2) {
return t1.toString().compareTo(t2.toString());
}
};
// A flag set on enum definition tree nodes
public static final int ENUMDECL = 1;
public static final int NOT_ENUMDECL = 0;
final JSTypeRegistry registry;
JSType(JSTypeRegistry registry) {
this.registry = registry;
}
/**
* Utility method for less verbose code.
*/
JSType getNativeType(JSTypeNative typeId) {
return registry.getNativeType(typeId);
}
/**
* Gets the docInfo for this type. By default, documentation cannot be
* attached to arbitrary types. This must be overridden for
* programmer-defined types.
*/
public JSDocInfo getJSDocInfo() {
return null;
}
/**
* Returns a user meaningful label for the JSType instance. For example,
* Functions and Enums will return their declaration name (if they have one).
* Some types will not have a meaningful display name. Calls to
* hasDisplayName() will return true IFF getDisplayName() will return null
* or a zero length string.
*
* @return the display name of the type, or null if one is not available
*/
public String getDisplayName() {
return null;
}
/**
* @return true if the JSType has a user meaningful label.
*/
public boolean hasDisplayName() {
String displayName = getDisplayName();
return displayName != null && !displayName.isEmpty();
}
public boolean isNoType() {
return false;
}
public boolean isNoResolvedType() {
return false;
}
public boolean isNoObjectType() {
return false;
}
public final boolean isEmptyType() {
return isNoType() || isNoObjectType() || isNoResolvedType() ||
(registry.getNativeFunctionType(
JSTypeNative.LE
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>
*/
public boolean isSubtype(JSType that) {
return isSubtypeHelper(this, that);
}
/**
* Whether this type is meaningfully different from {@code that} type.
* This is a trickier check than pure equality, because it has to properly
* handle unknown types.
*
* @see <a href="http://www.youtube.com/watch?v=_RpSv3HjpEw">Unknown
* unknowns</a>
*/
public boolean differsFrom(JSType that) {
// if there are no unknowns, just use normal equality.
if (!this.isUnknownType() && !that.isUnknownType()) {
return !this.isEquivalentTo(that);
}
// otherwise, they're different iff one is unknown and the other is not.
return this.isUnknownType() ^ that.isUnknownType();
}
/**
* A generic implementation meant to be used as a helper for common subtyping
* cases.
*/
static boolean isSubtypeHelper(JSType thisType, JSType thatType) {
// unknown
if (thatType.isUnknownType()) {
return true;
}
// equality
if (thisType.isEquivalentTo(thatType)) {
return true;
}
// all type
if (thatType.isAllType()) {
return true;
}
// unions
if (thatType.isUnionType()) {
UnionType union = thatType.toMaybeUnionType();
for (JSType element : union.alternates) {
if (thisType.isSubtype(element)) {
return true;
}
}
}
// proxy types
if (thatType instanceof ProxyObjectType) {
return thisType.isSubtype(
((ProxyObjectType) thatType).getReferencedTypeInternal());
}
return false;
}
/**
* Visit this type with the given visitor.
* @see com.google.javascript.rhino.jstype.Visitor
* @return the value returned by the visitor
*/
public abstract <T> T visit(Visitor<T> visitor);
/**
* Force this type to resolve, even if the registry is in a lazy
* resolving mode.
* @see #resolve
*/
public final JSType forceResolve(ErrorReporter t, StaticScope<JSType> scope) {
ResolveMode oldResolveMode = registry.getResolveMode();
registry.setResolveMode(ResolveMode.IMMEDIATE);
JSType result = resolve(t, scope);
registry.setResolveMode(oldResolveMode);
return result;
}
/**
* Resolve this type in the given scope.
*
* The returned value must be equal to {@code this}, as defined by
* {@link #isEquivalentTo}. It may or may not be the same object. This method
* may modify the internal state of {@code this}, as long as it does
* so in a way that preserves Object equality.
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> *
* For efficiency, we should only resolve a type once per compilation job.
* For incremental compilations, one compilation job may need the
* artifacts from a previous generation, so we will eventually need
* a generational flag instead of a boolean one.
*/
public final JSType resolve(ErrorReporter t, StaticScope<JSType> scope) {
if (resolved) {
// TODO(nicksantos): Check to see if resolve() looped back on itself.
// Preconditions.checkNotNull(resolveResult);
if (resolveResult == null) {
return registry.getNativeType(JSTypeNative.UNKNOWN_TYPE);
}
return resolveResult;
}
resolved = true;
resolveResult = resolveInternal(t, scope);
resolveResult.setResolvedTypeInternal(resolveResult);
return resolveResult;
}
/**
* @see #resolve
*/
abstract JSType resolveInternal(ErrorReporter t, StaticScope<JSType> scope);
void setResolvedTypeInternal(JSType type) {
resolveResult = type;
resolved = true;
}
/** Whether the type has been resolved. */
public final boolean isResolved() {
return resolved;
}
/** Clears the resolved field. */
public final void clearResolved() {
resolved = false;
resolveResult = null;
}
/**
* A null-safe resolve.
* @see #resolve
*/
static final JSType safeResolve(
JSType type, ErrorReporter t, StaticScope<JSType> scope) {
return type == null ? null : type.resolve(t, scope);
}
/**
* Certain types have constraints on them at resolution-time.
* For example, a type in an {@code @extends} annotation must be an
* object. Clients should inject a validator that emits a warning
* if the type does not validate, and return false.
*/
public boolean setValidator(Predicate<JSType> validator) {
return validator.apply(this);
}
public static class TypePair {
public final JSType typeA;
public final JSType typeB;
public TypePair(JSType typeA, JSType typeB) {
this.typeA = typeA;
this.typeB = typeB;
}
}
/**
* A string representation of this type, suitable for printing
* in warnings.
*/
@Override
public String toString() {
return toStringHelper(false);
}
/**
* A hash code function for diagnosing complicated issues
* around type-identity.
*/
public String toDebugHashCodeString() {
return "{" + this.hashCode() + "}";
}
/**
* A string representation of this type, suitable for printing
* in type annotations at code generation time.
*/
public final String toAnnotationString() {
return toStringHelper(true);
}
/**
* @param forAnnotations Whether this is for use in code generator
* annotations. Otherwise, it
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> paramType.hashCode();
}
param = param.getNext();
}
}
return hashCode;
}
@Override
public JSType getLeastSupertype(JSType that) {
throw new UnsupportedOperationException();
}
@Override
public JSType getGreatestSubtype(JSType that) {
throw new UnsupportedOperationException();
}
@Override
public TernaryValue testForEquality(JSType that) {
throw new UnsupportedOperationException();
}
@Override
public <T> T visit(Visitor<T> visitor) {
throw new UnsupportedOperationException();
}
@Override
public BooleanLiteralSet getPossibleToBooleanOutcomes() {
return BooleanLiteralSet.TRUE;
}
@Override
JSType resolveInternal(ErrorReporter t, StaticScope<JSType> scope) {
returnType = safeResolve(returnType, t, scope);
if (parameters != null) {
for (Node paramNode = parameters.getFirstChild();
paramNode != null; paramNode = paramNode.getNext()) {
paramNode.setJSType(paramNode.getJSType().resolve(t, scope));
}
}
return this;
}
boolean hasUnknownParamsOrReturn() {
if (parameters != null) {
for (Node paramNode = parameters.getFirstChild();
paramNode != null; paramNode = paramNode.getNext()) {
JSType type = paramNode.getJSType();
if (type == null || type.isUnknownType()) {
return true;
}
}
}
return returnType == null || returnType.isUnknownType();
}
@Override
String toStringHelper(boolean forAnnotations) {
return super.toString();
}
@Override
public boolean hasAnyTemplateInternal() {
return returnType.hasAnyTemplate()
|| hasTemplatedParameterType();
}
private boolean hasTemplatedParameterType() {
if (parameters != null) {
for (Node paramNode = parameters.getFirstChild();
paramNode != null; paramNode = paramNode.getNext()) {
JSType type = paramNode.getJSType();
if (type != null && type.hasAnyTemplate()) {
return true;
}
}
}
return false;
}
}
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>ocInfoBuilder} which provides early incompatibility detection.</p>
*
*/
public class JSDocInfo implements Serializable {
private static final long serialVersionUID = 1L;
/**
* Visibility categories. The {@link Visibility#ordinal()} can be used as a
* numerical indicator of privacy, where 0 is the most private. This means
* that the {@link Visibility#compareTo} method can be used to
* determine if a visibility is more permissive than another.
*/
public enum Visibility {
PRIVATE,
PROTECTED,
PUBLIC,
// If visibility is not specified, we just assume that visibility
// is inherited from the super class.
INHERITED
}
private static final class LazilyInitializedInfo implements Serializable {
private static final long serialVersionUID = 1L;
// Function information
JSTypeExpression baseType = null;
List<JSTypeExpression> extendedInterfaces = null;
List<JSTypeExpression> implementedInterfaces = null;
Map<String, JSTypeExpression> parameters = null;
List<JSTypeExpression> thrownTypes = null;
ImmutableList<String> templateTypeNames = null;
// Other information
String description = null;
String meaning = null;
String deprecated = null;
String license = null;
Set<String> suppressions = null;
Set<String> modifies = null;
String lendsName = null;
}
private static final class LazilyInitializedDocumentation {
String sourceComment = null;
List<Marker> markers = null;
Map<String, String> parameters = null;
Map<JSTypeExpression, String> throwsDescriptions = null;
String blockDescription = null;
String fileOverview = null;
String returnDescription = null;
String version = null;
List<String> authors = null;
List<String> sees = null;
}
/**
* A piece of information (found in a marker) which contains a position
* with a string.
*/
public static class StringPosition extends SourcePosition<String> {
}
/**
* A piece of information (found in a marker) which contains a position
* with a string that has no leading or trailing whitespace.
*/
static class TrimmedStringPosition extends StringPosition {
@Override public void setItem(String item) {
Preconditions.checkArgument(
item.charAt(0) != ' ' &&
item.charAt(item.length() - 1) != ' ',
"String has leading or trailing whitespace");
super.setItem(item);
}
}
/**
* A piece of information (found in a marker) which contains a position
* with a name node.
*/
public static class NamePosition extends SourcePosition<Node> {}
/**
* A piece of information (found in a marker) which contains a position
* with a type expression syntax tree.
*/
public static class TypePosition extends SourcePosition<Node> {
private boolean brackets = false;
/** Returns whether the type has curly
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> braces around it. */
public boolean hasBrackets() {
return brackets;
}
void setHasBrackets(boolean newVal) {
brackets = newVal;
}
}
/**
* Defines a class for containing the parsing information
* for this JSDocInfo. For each annotation found in the
* JsDoc, a marker will be created indicating the annotation
* itself, the name of the annotation (if any; for example,
* a @param has a name, but a @return does not), the
* textual description found on that annotation and, if applicable,
* the type declaration. All this information is only collected
* if documentation collection is turned on.
*/
public static final class Marker {
private TrimmedStringPosition annotation = null;
private TrimmedStringPosition name = null;
private SourcePosition<Node> nameNode = null;
private StringPosition description = null;
private TypePosition type = null;
/**
* Gets the position information for the annotation name. (e.g., "param")
*/
public StringPosition getAnnotation() {
return annotation;
}
void setAnnotation(TrimmedStringPosition p) {
annotation = p;
}
/**
* Gets the position information for the name found
* in a @param tag.
* @deprecated Use #getNameNode
*/
@Deprecated
public StringPosition getName() {
return name;
}
void setName(TrimmedStringPosition p) {
name = p;
}
/**
* Gets the position information for the name found
* in an @param tag.
*/
public SourcePosition<Node> getNameNode() {
return nameNode;
}
void setNameNode(SourcePosition<Node> p) {
nameNode = p;
}
/**
* Gets the position information for the description found
* in a block tag.
*/
public StringPosition getDescription() {
return description;
}
void setDescription(StringPosition p) {
description = p;
}
/**
* Gets the position information for the type expression found
* in some block tags, like "@param" and "@return".
*/
public TypePosition getType() {
return type;
}
void setType(TypePosition p) {
type = p;
}
}
private LazilyInitializedInfo info = null;
private LazilyInitializedDocumentation documentation = null;
// The Node this JSDoc is associated with.
private Node associatedNode = null;
private Visibility visibility = null;
/**
* The {@link #isConstant()}, {@link #isConstructor()}, {@link #isInterface},
* {@link #isHidden()} and {@link #shouldPreserveTry()} flags as well as
* whether the {@link #type} field stores a value for {@link #getType()},
* {@link #getReturnType()} or {@link #getEnumParameterType()}.
*
* @see #setFlag(boolean, int)
* @see #getFlag(int)
*
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> private static final int MASK_EXTERNS = 0x00008000; // @externs
private static final int MASK_JAVADISPATCH = 0x00010000; // @javadispatch
private static final int MASK_NOCOMPILE = 0x00020000; // @nocompile
// @consistentIdGenerator
private static final int MASK_CONSISTIDGEN = 0x00040000;
// @idGenerator
private static final int MASK_IDGEN = 0x00080000;
private static final int MASK_EXPOSE = 0x00100000; // @expose
// 3 bit type field stored in the top 3 bits of the most significant
// nibble.
private static final int MASK_TYPEFIELD = 0xE0000000; // 1110...
private static final int TYPEFIELD_TYPE = 0x20000000; // 0010...
private static final int TYPEFIELD_RETURN = 0x40000000; // 0100...
private static final int TYPEFIELD_ENUM = 0x60000000; // 0110...
private static final int TYPEFIELD_TYPEDEF = 0x80000000; // 1000...
/**
* Creates a {@link JSDocInfo} object. This object should be created using
* a {@link JSDocInfoBuilder}.
*/
JSDocInfo(boolean includeDocumentation) {
this.includeDocumentation = includeDocumentation;
}
// Visible for testing.
public JSDocInfo() {}
void setConsistentIdGenerator(boolean value) {
setFlag(value, MASK_CONSISTIDGEN);
}
void setConstant(boolean value) {
setFlag(value, MASK_CONSTANT);
}
void setConstructor(boolean value) {
setFlag(value, MASK_CONSTRUCTOR);
}
void setDefine(boolean value) {
setFlag(value, MASK_DEFINE);
}
void setHidden(boolean value) {
setFlag(value, MASK_HIDDEN);
}
void setNoCheck(boolean value) {
setFlag(value, MASK_NOCHECK);
}
void setShouldPreserveTry(boolean value) {
setFlag(value, MASK_PRESERVETRY);
}
void setOverride(boolean value) {
setFlag(value, MASK_OVERRIDE);
}
void setNoAlias(boolean value) {
setFlag(value, MASK_NOALIAS);
}
// Visible for testing.
public void setDeprecated(boolean value) {
setFlag(value, MASK_DEPRECATED);
}
void setInterface(boolean
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> (hasType()
|| hasReturnType()
|| hasEnumParameterType()
|| hasTypedefType()
|| hasThisType()
|| getParameterCount() > 0
|| getFlag(MASK_CONSTANT
| MASK_CONSTRUCTOR
| MASK_DEFINE
| MASK_OVERRIDE
| MASK_NOALIAS
| MASK_DEPRECATED
| MASK_INTERFACE
| MASK_NOSHADOW
| MASK_IMPLICITCAST
| MASK_NOSIDEEFFECTS));
}
private boolean getFlag(int mask) {
return (bitset & mask) != 0x00;
}
// Visible for testing.
public void setVisibility(Visibility visibility) {
this.visibility = visibility;
}
private void lazyInitInfo() {
if (info == null) {
info = new LazilyInitializedInfo();
}
}
/**
* Lazily initializes the documentation information object, but only
* if the JSDocInfo was told to keep such information around.
*/
private boolean lazyInitDocumentation() {
if (!includeDocumentation) {
return false;
}
if (documentation == null) {
documentation = new LazilyInitializedDocumentation();
}
return true;
}
/**
* Adds a marker to the documentation (if it exists) and
* returns the marker. Returns null otherwise.
*/
Marker addMarker() {
if (!lazyInitDocumentation()) {
return null;
}
if (documentation.markers == null) {
documentation.markers = Lists.newArrayList();
}
Marker marker = new Marker();
documentation.markers.add(marker);
return marker;
}
/**
* Sets the deprecation reason.
*
* @param reason The deprecation reason
*/
boolean setDeprecationReason(String reason) {
lazyInitInfo();
if (info.deprecated != null) {
return false;
}
info.deprecated = reason;
return true;
}
/**
* Add a suppressed warning.
*/
public void addSuppression(String suppression) {
lazyInitInfo();
if (info.suppressions == null) {
info.suppressions = Sets.newHashSet();
}
info.suppressions.add(suppression);
}
/**
* Sets suppressed warnings.
* @param suppressions A list of suppressed warning types.
*/
boolean setSuppressions(Set<String> suppressions) {
lazyInitInfo();
if (info.suppressions != null) {
return false;
}
info.suppressions = suppressions;
return true;
}
/**
* Add modifies values.
*/
void addModifies(String modifies) {
lazyInitInfo();
if (info.modifies == null) {
info.modifies = Sets.newHashSet();
}
info.modifies.add(modifies);
}
/**
* Sets modifies values.
* @param modifies A list of modifies types.
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>
* Returns whether the parameter is defined.
*/
public boolean hasParameter(String parameter) {
if (info == null || info.parameters == null) {
return false;
}
return info.parameters.containsKey(parameter);
}
/**
* Returns whether the parameter has an attached type.
*
* @return {@code true} if the parameter has an attached type, {@code false}
* if the parameter has no attached type or does not exist.
*/
public boolean hasParameterType(String parameter) {
return getParameterType(parameter) != null;
}
/**
* Returns the set of names of the defined parameters. The iteration order
* of the returned set is not the order in which parameters are defined.
*
* @return the set of names of the defined parameters. The returned set is
* immutable.
*/
public Set<String> getParameterNames() {
if (info == null || info.parameters == null) {
return ImmutableSet.of();
}
return ImmutableSet.copyOf(info.parameters.keySet());
}
/**
* Gets the number of parameters defined.
*/
public int getParameterCount() {
if (info == null || info.parameters == null) {
return 0;
}
return info.parameters.size();
}
void setType(JSTypeExpression type) {
setType(type, TYPEFIELD_TYPE);
}
void setReturnType(JSTypeExpression type) {
setType(type, TYPEFIELD_RETURN);
}
void setEnumParameterType(JSTypeExpression type) {
setType(type, TYPEFIELD_ENUM);
}
void setTypedefType(JSTypeExpression type) {
setType(type, TYPEFIELD_TYPEDEF);
}
private void setType(JSTypeExpression type, int mask) {
if ((bitset & MASK_TYPEFIELD) != 0) {
throw new IllegalStateException(
"API tried to add two incompatible type tags. " +
"This should have been blocked and emitted a warning.");
}
this.bitset = (bitset & MASK_FLAGS) | mask;
this.type = type;
}
/**
* Returns the list of thrown types.
*/
public List<JSTypeExpression> getThrownTypes() {
if (info == null || info.thrownTypes == null) {
return ImmutableList.of();
}
return Collections.unmodifiableList(info.thrownTypes);
}
/**
* Returns whether a type, specified using the {@code @type} annotation, is
* present on this JSDoc.
*/
public boolean hasType() {
return hasType(TYPEFIELD_TYPE);
}
/**
* Returns whether an enum parameter type, specified using the {@code @enum}
* annotation, is present on this JSDoc.
*/
public boolean hasEnumParameterType() {
return hasType(TYPEFIELD_ENUM);
}
/**
* Returns whether a typedef parameter
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> type, specified using the
* {@code @typedef} annotation, is present on this JSDoc.
*/
public boolean hasTypedefType() {
return hasType(TYPEFIELD_TYPEDEF);
}
/**
* Returns whether this {@link JSDocInfo} contains a type for {@code @return}
* annotation.
*/
public boolean hasReturnType() {
return hasType(TYPEFIELD_RETURN);
}
private boolean hasType(int mask) {
return (bitset & MASK_TYPEFIELD) == mask;
}
/**
* Gets the type specified by the {@code @type} annotation.
*/
public JSTypeExpression getType() {
return getType(TYPEFIELD_TYPE);
}
/**
* Gets the return type specified by the {@code @return} annotation.
*/
public JSTypeExpression getReturnType() {
return getType(TYPEFIELD_RETURN);
}
/**
* Gets the enum parameter type specified by the {@code @enum} annotation.
*/
public JSTypeExpression getEnumParameterType() {
return getType(TYPEFIELD_ENUM);
}
/**
* Gets the typedef type specified by the {@code @type} annotation.
*/
public JSTypeExpression getTypedefType() {
return getType(TYPEFIELD_TYPEDEF);
}
private JSTypeExpression getType(int typefield) {
if ((MASK_TYPEFIELD & bitset) == typefield) {
return type;
} else {
return null;
}
}
/**
* Gets the type specified by the {@code @this} annotation.
*/
public JSTypeExpression getThisType() {
return thisType;
}
/**
* Sets the type specified by the {@code @this} annotation.
*/
void setThisType(JSTypeExpression type) {
this.thisType = type;
}
/**
* Returns whether this {@link JSDocInfo} contains a type for {@code @this}
* annotation.
*/
public boolean hasThisType() {
return thisType != null;
}
void setBaseType(JSTypeExpression type) {
lazyInitInfo();
info.baseType = type;
}
/**
* Gets the base type specified by the {@code @extends} annotation.
*/
public JSTypeExpression getBaseType() {
return (info == null) ? null : info.baseType;
}
/**
* Gets the description specified by the {@code @desc} annotation.
*/
public String getDescription() {
return (info == null) ? null : info.description;
}
void setDescription(String desc) {
lazyInitInfo();
info.description = desc;
}
/**
* Gets the meaning specified by the {@code @meaning} annotation.
*
* In localization systems, two messages with the same content but
* different "meanings" may be translated differently. By default, we
* use the name of the variable that the message is initialized to as
* the "mean
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>defineDeclaredProperty(methodName,
new FunctionBuilder(registry).withReturnType(returnType).build(),
null);
}
protected JSType createUnionType(JSType... variants) {
return registry.createUnionType(variants);
}
protected RecordTypeBuilder createRecordTypeBuilder() {
return new RecordTypeBuilder(registry);
}
protected JSType createNullableType(JSType type) {
return registry.createNullableType(type);
}
protected JSType createOptionalType(JSType type) {
return registry.createOptionalType(type);
}
/**
* Asserts that a Node representing a type expression resolves to the
* correct {@code JSType}.
*/
protected void assertTypeEquals(JSType expected, Node actual) {
assertTypeEquals(expected, new JSTypeExpression(actual, ""));
}
/**
* Asserts that a a type expression resolves to the correct {@code JSType}.
*/
protected void assertTypeEquals(JSType expected, JSTypeExpression actual) {
assertEquals(expected, resolve(actual));
}
/**
* Resolves a type expression, expecting the given warnings.
*/
protected JSType resolve(JSTypeExpression n, String... warnings) {
errorReporter.setWarnings(warnings);
return n.evaluate(null, registry);
}
/**
* A definition of all extern types. This should be kept in sync with
* javascript/externs/es3.js. This is used to check that the built-in types
* declared in {@link JSTypeRegistry} have the same type as that in the
* externs. It can also be used for any tests that want to use built-in types
* in their externs.
*/
public static final String ALL_NATIVE_EXTERN_TYPES =
"/**\n"
+ " * @constructor\n"
+ " * @param {*} opt_value\n"
+ " */\n"
+ "function Object(opt_value) {}\n"
+ "\n"
+ "/**\n"
+ " * @constructor\n"
+ " * @extends {Object}\n"
+ " * @param {*} var_args\n"
+ " */\n"
+ "\n"
+ "function Function(var_args) {}\n"
+ "/**\n"
+ " * @constructor\n"
+ " * @extends {Object}\n"
+ " * @param {*} var_args\n"
+ " * @return {!Array}\n"
+ " */\n"
+ "function Array(var_args) {}\n"
+ "\n"
+ "/**\n"
+ " * @constructor\n"
+ " * @param {*} opt_value\n"
+ " * @return {boolean}\n"
+ " */\n"
+ "function Boolean(opt_value) {}\n"
+ "\n"
+ "/**\n
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>
* JSDOC_TYPE_PROP's for the compile-time type of each argument.
*/
public class FunctionType extends PrototypeObjectType {
private static final long serialVersionUID = 1L;
private enum Kind {
ORDINARY,
CONSTRUCTOR,
INTERFACE
}
/**
* {@code [[Call]]} property.
*/
private ArrowType call;
/**
* The {@code prototype} property. This field is lazily initialized by
* {@code #getPrototype()}. The most important reason for lazily
* initializing this field is that there are cycles in the native types
* graph, so some prototypes must temporarily be {@code null} during
* the construction of the graph.
*
* If non-null, the type must be a PrototypeObjectType.
*/
private Property prototypeSlot;
/**
* Whether a function is a constructor, an interface, or just an ordinary
* function.
*/
private final Kind kind;
/**
* The type of {@code this} in the scope of this function.
*/
private ObjectType typeOfThis;
/**
* The function node which this type represents. It may be {@code null}.
*/
private Node source;
/**
* The interfaces directly implemented by this function (for constructors)
* It is only relevant for constructors. May not be {@code null}.
*/
private List<ObjectType> implementedInterfaces = ImmutableList.of();
/**
* The interfaces directly extended by this function (for interfaces)
* It is only relevant for constructors. May not be {@code null}.
*/
private List<ObjectType> extendedInterfaces = ImmutableList.of();
/**
* The types which are subtypes of this function. It is only relevant for
* constructors and may be {@code null}.
*/
private List<FunctionType> subTypes;
/**
* The template type name. May be {@code null}.
*/
private final ImmutableList<String> templateTypeNames;
/** Creates an instance for a function that might be a constructor. */
FunctionType(JSTypeRegistry registry, String name, Node source,
ArrowType arrowType, ObjectType typeOfThis,
ImmutableList<String> templateTypeNames,
boolean isConstructor, boolean nativeType) {
super(registry, name,
registry.getNativeObjectType(JSTypeNative.FUNCTION_INSTANCE_TYPE),
nativeType);
setPrettyPrint(true);
Preconditions.checkArgument(source == null ||
Token.FUNCTION == source.getType());
Preconditions.checkNotNull(arrowType);
this.source = source;
this.kind = isConstructor ? Kind.CONSTRUCTOR : Kind.ORDINARY;
if (isConstructor) {
this.typeOfThis = typeOfThis != null ?
typeOfThis : new InstanceObjectType(registry, this, nativeType);
} else {
this.typeOfThis = typeOfThis != null ?
typeOfThis :
registry.getNativeObjectType(JSTypeNative.UNKNOWN_TYPE);
}
this.call = arrowType;
this.
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>templateTypeNames = templateTypeNames != null
? templateTypeNames : ImmutableList.<String>of();
}
/** Creates an instance for a function that is an interface. */
private FunctionType(JSTypeRegistry registry, String name, Node source) {
super(registry, name,
registry.getNativeObjectType(JSTypeNative.FUNCTION_INSTANCE_TYPE));
setPrettyPrint(true);
Preconditions.checkArgument(source == null ||
Token.FUNCTION == source.getType());
Preconditions.checkArgument(name != null);
this.source = source;
this.call = new ArrowType(registry, new Node(Token.PARAM_LIST), null);
this.kind = Kind.INTERFACE;
this.typeOfThis = new InstanceObjectType(registry, this);
this.templateTypeNames = ImmutableList.of();
}
/** Creates an instance for a function that is an interface. */
static FunctionType forInterface(
JSTypeRegistry registry, String name, Node source) {
return new FunctionType(registry, name, source);
}
@Override
public boolean isInstanceType() {
// The universal constructor is its own instance, bizarrely.
return isEquivalentTo(registry.getNativeType(U2U_CONSTRUCTOR_TYPE));
}
@Override
public boolean isConstructor() {
return kind == Kind.CONSTRUCTOR;
}
@Override
public boolean isInterface() {
return kind == Kind.INTERFACE;
}
@Override
public boolean isOrdinaryFunction() {
return kind == Kind.ORDINARY;
}
@Override
public FunctionType toMaybeFunctionType() {
return this;
}
@Override
public boolean canBeCalled() {
return true;
}
public boolean hasImplementedInterfaces() {
if (!implementedInterfaces.isEmpty()){
return true;
}
FunctionType superCtor = isConstructor() ?
getSuperClassConstructor() : null;
if (superCtor != null) {
return superCtor.hasImplementedInterfaces();
}
return false;
}
public Iterable<Node> getParameters() {
Node n = getParametersNode();
if (n != null) {
return n.children();
} else {
return Collections.emptySet();
}
}
/** Gets an LP node that contains all params. May be null. */
public Node getParametersNode() {
return call.parameters;
}
/** Gets the minimum number of arguments that this function requires. */
public int getMinArguments() {
// NOTE(nicksantos): There are some native functions that have optional
// parameters before required parameters. This algorithm finds the position
// of the last required parameter.
int i = 0;
int min = 0;
for (Node n : getParameters()) {
i++;
if (!n.isOptionalArg() && !n.isVarArgs()) {
min = i;
}
}
return min;
}
/**
* Gets the maximum number of arguments
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> that this function requires,
* or Integer.MAX_VALUE if this is a variable argument function.
*/
public int getMaxArguments() {
Node params = getParametersNode();
if (params != null) {
Node lastParam = params.getLastChild();
if (lastParam == null || !lastParam.isVarArgs()) {
return params.getChildCount();
}
}
return Integer.MAX_VALUE;
}
public JSType getReturnType() {
return call.returnType;
}
public boolean isReturnTypeInferred() {
return call.returnTypeInferred;
}
/** Gets the internal arrow type. For use by subclasses only. */
ArrowType getInternalArrowType() {
return call;
}
@Override
public Property getSlot(String name) {
if ("prototype".equals(name)) {
// Lazy initialization of the prototype field.
getPrototype();
return prototypeSlot;
} else {
return super.getSlot(name);
}
}
/**
* Includes the prototype iff someone has created it. We do not want
* to expose the prototype for ordinary functions.
*/
@Override
public Set<String> getOwnPropertyNames() {
if (prototypeSlot == null) {
return super.getOwnPropertyNames();
} else {
Set<String> names = Sets.newHashSet("prototype");
names.addAll(super.getOwnPropertyNames());
return names;
}
}
/**
* Gets the {@code prototype} property of this function type. This is
* equivalent to {@code (ObjectType) getPropertyType("prototype")}.
*/
public ObjectType getPrototype() {
// lazy initialization of the prototype field
if (prototypeSlot == null) {
String refName = getReferenceName();
if (refName == null) {
// Someone is trying to access the prototype of a structural function.
// We don't want to give real properties to this prototype, because
// then it would propagate to all structural functions.
setPrototypeNoCheck(
registry.getNativeObjectType(JSTypeNative.UNKNOWN_TYPE),
null);
} else {
setPrototype(
new PrototypeObjectType(
registry,
this.getReferenceName() + ".prototype",
registry.getNativeObjectType(OBJECT_TYPE),
isNativeObjectType()),
null);
}
}
return (ObjectType) prototypeSlot.getType();
}
/**
* Sets the prototype, creating the prototype object from the given
* base type.
* @param baseType The base type.
*/
public void setPrototypeBasedOn(ObjectType baseType) {
setPrototypeBasedOn(baseType, null);
}
void setPrototypeBasedOn(ObjectType baseType, Node propertyNode) {
// This is a bit weird. We need to successfully handle these
// two cases:
// Foo.prototype = new Bar();
// and
// Foo.prototype = {baz: 3};
// In the first case, we do not want new properties to get
// added to
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> Bar. In the second case, we do want new properties
// to get added to the type of the anonymous object.
//
// We handle this by breaking it into two cases:
//
// In the first case, we create a new PrototypeObjectType and set
// its implicit prototype to the type being assigned. This ensures
// that Bar will not get any properties of Foo.prototype, but properties
// later assigned to Bar will get inherited properly.
//
// In the second case, we just use the anonymous object as the prototype.
if (baseType.hasReferenceName() ||
isNativeObjectType() ||
baseType.isFunctionPrototypeType()) {
baseType = new PrototypeObjectType(
registry, this.getReferenceName() + ".prototype", baseType);
}
setPrototype(baseType, propertyNode);
}
/**
* Sets the prototype.
* @param prototype the prototype. If this value is {@code null} it will
* silently be discarded.
*/
boolean setPrototype(ObjectType prototype, Node propertyNode) {
if (prototype == null) {
return false;
}
// getInstanceType fails if the function is not a constructor
if (isConstructor() && prototype == getInstanceType()) {
return false;
}
return setPrototypeNoCheck(prototype, propertyNode);
}
/** Set the prototype without doing any sanity checks. */
private boolean setPrototypeNoCheck(ObjectType prototype, Node propertyNode) {
ObjectType oldPrototype = prototypeSlot == null
? null : (ObjectType) prototypeSlot.getType();
boolean replacedPrototype = oldPrototype != null;
this.prototypeSlot = new Property("prototype", prototype, true,
propertyNode == null ? source : propertyNode);
prototype.setOwnerFunction(this);
if (oldPrototype != null) {
// Disassociating the old prototype makes this easier to debug--
// we don't have to worry about two prototypes running around.
oldPrototype.setOwnerFunction(null);
}
if (isConstructor() || isInterface()) {
FunctionType superClass = getSuperClassConstructor();
if (superClass != null) {
superClass.addSubType(this);
}
if (isInterface()) {
for (ObjectType interfaceType : getExtendedInterfaces()) {
if (interfaceType.getConstructor() != null) {
interfaceType.getConstructor().addSubType(this);
}
}
}
}
if (replacedPrototype) {
clearCachedValues();
}
return true;
}
/**
* Returns all interfaces implemented by a class or its superclass and any
* superclasses for any of those interfaces. If this is called before all
* types are resolved, it may return an incomplete set.
*/
public Iterable<ObjectType> getAllImplementedInterfaces() {
// Store them in a linked hash set, so that the compile job is
// deterministic.
Set<ObjectType> interfaces = Sets.newLinkedHashSet();
for (ObjectType type : getImplementedInterfaces()) {
addRelatedInterfaces(type
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS>(true);
}
}
builder.withParamsNode(params);
}
return builder.build();
}
@Override
boolean defineProperty(String name, JSType type,
boolean inferred, Node propertyNode) {
if ("prototype".equals(name)) {
ObjectType objType = type.toObjectType();
if (objType != null) {
if (prototypeSlot != null &&
objType.isEquivalentTo(prototypeSlot.getType())) {
return true;
}
this.setPrototypeBasedOn(objType, propertyNode);
return true;
} else {
return false;
}
}
return super.defineProperty(name, type, inferred, propertyNode);
}
/**
* Computes the supremum or infimum of two functions.
* Because sup() and inf() share a lot of logic for functions, we use
* a single helper.
* @param leastSuper If true, compute the supremum of {@code this} with
* {@code that}. Otherwise, compute the infimum.
* @return The least supertype or greatest subtype.
*/
FunctionType supAndInfHelper(FunctionType that, boolean leastSuper) {
// NOTE(nicksantos): When we remove the unknown type, the function types
// form a lattice with the universal constructor at the top of the lattice,
// and the LEAST_FUNCTION_TYPE type at the bottom of the lattice.
//
// When we introduce the unknown type, it's much more difficult to make
// heads or tails of the partial ordering of types, because there's no
// clear hierarchy between the different components (parameter types and
// return types) in the ArrowType.
//
// Rather than make the situation more complicated by introducing new
// types (like unions of functions), we just fallback on the simpler
// approach of getting things right at the top and the bottom of the
// lattice.
//
// If there are unknown parameters or return types making things
// ambiguous, then sup(A, B) is always the top function type, and
// inf(A, B) is always the bottom function type.
Preconditions.checkNotNull(that);
if (isEquivalentTo(that)) {
return this;
}
// If these are ordinary functions, then merge them.
// Don't do this if any of the params/return
// values are unknown, because then there will be cycles in
// their local lattice and they will merge in weird ways.
if (isOrdinaryFunction() && that.isOrdinaryFunction() &&
!this.call.hasUnknownParamsOrReturn() &&
!that.call.hasUnknownParamsOrReturn()) {
// Check for the degenerate case, but double check
// that there's not a cycle.
boolean isSubtypeOfThat = this.isSubtype(that);
boolean isSubtypeOfThis = that.isSubtype(this);
if (isSubtypeOfThat && !isSubtypeOfThis) {
return leastSuper
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> }
/**
* Sets the instance type. This should only be used for special
* native types.
*/
void setInstanceType(ObjectType instanceType) {
typeOfThis = instanceType;
}
/**
* Returns whether this function type has an instance type.
*/
public boolean hasInstanceType() {
return isConstructor() || isInterface();
}
/**
* Gets the type of {@code this} in this function.
*/
@Override
public ObjectType getTypeOfThis() {
return typeOfThis.isNoObjectType() ?
registry.getNativeObjectType(JSTypeNative.UNKNOWN_TYPE) : typeOfThis;
}
/**
* Gets the source node or null if this is an unknown function.
*/
public Node getSource() {
return source;
}
/**
* Sets the source node.
*/
public void setSource(Node source) {
if (prototypeSlot != null) {
// NOTE(bashir): On one hand when source is null we want to drop any
// references to old nodes retained in prototypeSlot. On the other hand
// we cannot simply drop prototypeSlot, so we retain all information
// except the propertyNode for which we use an approximation! These
// details mostly matter in hot-swap passes.
if (source == null || prototypeSlot.getNode() == null) {
prototypeSlot = new Property(prototypeSlot.getName(),
prototypeSlot.getType(), prototypeSlot.isTypeInferred(), source);
}
}
this.source = source;
}
/** Adds a type to the list of subtypes for this type. */
private void addSubType(FunctionType subType) {
if (subTypes == null) {
subTypes = Lists.newArrayList();
}
subTypes.add(subType);
}
@Override
public void clearCachedValues() {
super.clearCachedValues();
if (subTypes != null) {
for (FunctionType subType : subTypes) {
subType.clearCachedValues();
}
}
if (!isNativeObjectType()) {
if (hasInstanceType()) {
getInstanceType().clearCachedValues();
}
if (prototypeSlot != null) {
((ObjectType) prototypeSlot.getType()).clearCachedValues();
}
}
}
/**
* Returns a list of types that are subtypes of this type. This is only valid
* for constructor functions, and may be null. This allows a downward
* traversal of the subtype graph.
*/
public List<FunctionType> getSubTypes() {
return subTypes;
}
@Override
public boolean hasCachedValues() {
return prototypeSlot != null || super.hasCachedValues();
}
/**
* Gets the template type name.
*/
public ImmutableList<String> getTemplateTypeNames() {
return templateTypeNames;
}
@Override
JSType resolveInternal(ErrorReporter t, StaticScope<JSType> scope) {
setResolvedTypeInternal(this);
call = (ArrowType)
Closure, 17
<FILEB>
<CHANGES>
JSDocInfo rValueInfo = rValue.getJSDocInfo();
if (rValueInfo!= null && rValueInfo.hasType()) {
<CHANGEE>
<CHANGES>
return rValueInfo.getType().evaluate(scope, typeRegistry);
} else if (rValue.getJSType()!= null
&&!rValue.getJSType().isUnknownType()) {
<CHANGEE>
<FILEE>
<FILEB>
JSType.toMaybeFunctionType(rValue.getJSType()), info, lValue)) {
return rValue.getJSType();
} else if (info != null) {
if (info.hasEnumParameterType()) {
if (rValue != null && rValue.isObjectLit()) {
return rValue.getJSType();
} else {
return createEnumTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
}
} else if (info.isConstructor() || info.isInterface()) {
return createFunctionTypeFromNodes(
rValue, lValue.getQualifiedName(), info, lValue);
} else {
// Check if this is constant, and if it has a known type.
if (info.isConstant()) {
JSType knownType = null;
if (rValue != null) {
<CHANGES>
if (rValue.getJSType() != null && !rValue.getJSType().isUnknownType()) {
<CHANGEE>
// If rValue has a type-cast, we use the type in the type-cast.
<CHANGES>
<CHANGEE>
// If rValue's type was already computed during scope creation,
// then we can safely use that.
return rValue.getJSType();
} else if (rValue.isOr()) {
// Check for a very specific JS idiom:
// var x = x || TYPE;
// This is used by Closure's base namespace for esoteric
// reasons.
Node firstClause = rValue.getFirstChild();
Node secondClause = firstClause.getNext();
boolean namesMatch = firstClause.isName()
&& lValue.isName()
<FILEE>
<SCANS> safeResolve(call, t, scope);
if (prototypeSlot != null) {
prototypeSlot.setType(
safeResolve(prototypeSlot.getType(), t, scope));
}
// Warning about typeOfThis if it doesn't resolve to an ObjectType
// is handled further upstream.
//
// TODO(nicksantos): Handle this correctly if we have a UnionType.
//
// TODO(nicksantos): In ES3, the run-time coerces "null" to the global
// activation object. In ES5, it leaves it as null. Just punt on this
// issue for now by coercing out null. This is complicated by the
// fact that when most people write @this {Foo}, they really don't
// mean "nullable Foo". For certain tags (like @extends) we de-nullify
// the name for them.
JSType maybeTypeOfThis = safeResolve(typeOfThis, t, scope);
if (maybeTypeOfThis != null) {
maybeTypeOfThis = maybeTypeOfThis.restrictByNotNullOrUndefined();
}
if (maybeTypeOfThis instanceof ObjectType) {
typeOfThis = (ObjectType) maybeTypeOfThis;
}
boolean changed = false;
ImmutableList.Builder<ObjectType> resolvedInterfaces =
ImmutableList.builder();
for (ObjectType iface : implementedInterfaces) {
ObjectType resolvedIface = (ObjectType) iface.resolve(t, scope);
resolvedInterfaces.add(resolvedIface);
changed |= (resolvedIface != iface);
}
if (changed) {
implementedInterfaces = resolvedInterfaces.build();
}
if (subTypes != null) {
for (int i = 0; i < subTypes.size(); i++) {
subTypes.set(
i, JSType.toMaybeFunctionType(subTypes.get(i).resolve(t, scope)));
}
}
return super.resolveInternal(t, scope);
}
@Override
public String toDebugHashCodeString() {
if (this == registry.getNativeType(JSTypeNative.FUNCTION_INSTANCE_TYPE)) {
return super.toDebugHashCodeString();
}
StringBuilder b = new StringBuilder(32);
b.append("function (");
int paramNum = call.parameters.getChildCount();
boolean hasKnownTypeOfThis = !typeOfThis.isUnknownType();
if (hasKnownTypeOfThis) {
b.append("this:");
b.append(getDebugHashCodeStringOf(typeOfThis));
}
if (paramNum > 0) {
if (hasKnownTypeOfThis) {
b.append(", ");
}
Node p = call.parameters.getFirstChild();
b.append(getDebugHashCodeStringOf(p.getJSType()));
p = p.getNext();
while (p != null) {
b.append(", ");
b.append(getDebugHashCodeStringOf(p.getJSType()));
p = p.getNext();
}
}
b.append("